jinja2
~~~~~~
- Jinja is a `sandboxed`_ template engine written in pure Python. It
- provides a `Django`_ like non-XML syntax and compiles templates into
- executable python code. It's basically a combination of Django templates
- and python code.
+ Jinja2 is a template engine written in pure Python. It provides a
+ Django inspired non-XML syntax but supports inline expressions and
+ an optional sandboxed environment.
Nutshell
--------
- Here a small example of a Jinja template::
+ Here a small example of a Jinja2 template::
{% extends 'base.html' %}
{% block title %}Memberlist{% endblock %}
{% block content %}
<ul>
{% for user in users %}
- <li><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
+ <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
- Philosophy
- ----------
- Application logic is for the controller but don't try to make the life
- for the template designer too hard by giving him too few functionality.
-
- For more informations visit the new `jinja webpage`_ and `documentation`_.
-
- Note
- ----
-
- This is the Jinja 1.0 release which is completely incompatible with the
- old "pre 1.0" branch. The old branch will still receive security updates
- and bugfixes but the 1.0 branch will be the only version that receives
- support.
-
- If you have an application that uses Jinja 0.9 and won't be updated in
- the near future the best idea is to ship a Jinja 0.9 checkout together
- with the application.
-
- The `Jinja tip`_ is installable via `easy_install` with ``easy_install
- Jinja==dev``.
-
- .. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
- .. _Django: http://www.djangoproject.com/
- .. _jinja webpage: http://jinja.pocoo.org/
- .. _documentation: http://jinja.pocoo.org/documentation/index.html
- .. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
-
-
- :copyright: 2008 by Armin Ronacher.
+ :copyright: 2008 by Armin Ronacher, Christoph Hack.
:license: BSD, see LICENSE for more details.
"""
-from jinja2.environment import Environment
+__docformat__ = 'restructuredtext en'
+try:
+ __version__ = __import__('pkg_resources') \
+ .get_distribution('Jinja2').version
+except:
+ __version__ = 'unknown'
+
+# high level interface
+from jinja2.environment import Environment, Template
+
+# loaders
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
- DictLoader
+ DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader
+
+# undefined types
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+
+# decorators and public utilities
from jinja2.filters import environmentfilter, contextfilter
-from jinja2.utils import Markup, escape, contextfunction
+from jinja2.utils import Markup, escape, environmentfunction, contextfunction
self.blocks[block.name] = block
# generate the root render function.
- self.writeline('def root(globals, environment=environment'
- ', standalone=False):', extra=1)
- self.indent()
- self.writeline('context = TemplateContext(environment, globals, %r, '
- 'blocks, standalone)' % self.name)
+ self.writeline('def root(context, environment=environment'
+ '):', extra=1)
if have_extends:
- self.writeline('parent_root = None')
- self.outdent()
+ self.indent()
+ self.writeline('parent_template = None')
+ self.outdent()
# process the root
frame = Frame()
frame.toplevel = frame.rootlevel = True
self.indent()
self.pull_locals(frame, indent=False)
- self.writeline('yield context')
self.blockvisit(node.body, frame, indent=False)
self.outdent()
if have_extends:
if not self.has_known_extends:
self.indent()
- self.writeline('if parent_root is not None:')
+ self.writeline('if parent_template is not None:')
self.indent()
- self.writeline('stream = parent_root(context)')
- self.writeline('stream.next()')
- self.writeline('for event in stream:')
+ self.writeline('for event in parent_template.'
+ 'root_render_func(context):')
self.indent()
self.writeline('yield event')
- self.outdent(1 + self.has_known_extends)
+ self.outdent(2 + (not self.has_known_extends))
# at this point we now have the blocks collected and can visit them too.
for name, block in self.blocks.iteritems():
block_frame.inspect(block.body)
block_frame.block = name
block_frame.identifiers.add_special('super')
- block_frame.name_overrides['super'] = 'context.super(%r)' % name
+ block_frame.name_overrides['super'] = 'context.super(%r, ' \
+ 'block_%s)' % (name, name)
self.writeline('def block_%s(context, environment=environment):'
% name, block, 1)
self.pull_locals(block_frame)
extra=1)
# add a function that returns the debug info
- self.writeline('def get_debug_info():', extra=1)
- self.indent()
- self.writeline('return %r' % self.debug_info)
+ self.writeline('debug_info = %r' % '&'.join('%s=%s' % x for x
+ in self.debug_info))
def visit_Block(self, node, frame):
"""Call a block and register it for the template."""
if self.has_known_extends:
return
if self.extends_so_far > 0:
- self.writeline('if parent_root is None:')
+ self.writeline('if parent_template is None:')
self.indent()
level += 1
self.writeline('for event in context.blocks[%r][-1](context):' % node.name)
# time too, but i welcome it not to confuse users by throwing the
# same error at different times just "because we can".
if not self.has_known_extends:
- self.writeline('if parent_root is not None:')
+ self.writeline('if parent_template is not None:')
self.indent()
self.writeline('raise TemplateRuntimeError(%r)' %
'extended multiple times')
raise CompilerExit()
self.outdent()
- self.writeline('parent_root = environment.get_template(', node, 1)
+ self.writeline('parent_template = environment.get_template(', node, 1)
self.visit(node.template, frame)
- self.write(', %r).root_render_func' % self.name)
+ self.write(', %r)' % self.name)
+ self.writeline('for name, parent_block in parent_template.'
+ 'blocks.iteritems():')
+ self.indent()
+ self.writeline('context.blocks.setdefault(name, []).'
+ 'insert(0, parent_block)')
+ self.outdent()
# if this extends statement was in the root level we can take
# advantage of that information and simplify the generated code
self.write(')')
return
- self.writeline('included_stream = environment.get_template(', node)
+ self.writeline('included_template = environment.get_template(', node)
self.visit(node.template, frame)
- self.write(').root_render_func(context, standalone=True)')
- self.writeline('included_context = included_stream.next()')
- self.writeline('for event in included_stream:')
+ self.write(')')
+ if frame.toplevel:
+ self.writeline('included_context = included_template.new_context('
+ 'context.get_root())')
+ self.writeline('for event in included_template.root_render_func('
+ 'included_context):')
+ else:
+ self.writeline('for event in included_template.root_render_func('
+ 'included_template.new_context(context.get_root())):')
self.indent()
if frame.buffer is None:
self.writeline('yield event')
# so that they don't appear in the output.
outdent_later = False
if frame.toplevel and self.extends_so_far != 0:
- self.writeline('if parent_root is None:')
+ self.writeline('if parent_template is None:')
self.indent()
outdent_later = True
# figure the real context out
real_locals = tb.tb_frame.f_locals.copy()
- locals = dict(real_locals.get('context', {}))
+ ctx = real_locals.get('context')
+ if ctx:
+ locals = ctx.get_all()
+ else:
+ locals = {}
for name, value in real_locals.iteritems():
if name.startswith('l_'):
locals[name[2:]] = value
from jinja2.parser import Parser
from jinja2.optimizer import optimize
from jinja2.compiler import generate
-from jinja2.runtime import Undefined
+from jinja2.runtime import Undefined, TemplateContext
from jinja2.debug import translate_exception
-from jinja2.utils import import_string
+from jinja2.utils import import_string, LRUCache
from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+# for direct template usage we have up to ten living environments
+_spontaneous_environments = LRUCache(10)
+
+
+def _get_spontaneous_environment(*args):
+ """Return a new spontaneus environment. A spontaneus environment is an
+ unnamed and unaccessable (in theory) environment that is used for
+ template generated from a string and not from the file system.
+ """
+ try:
+ env = _spontaneous_environments.get(args)
+ except TypeError:
+ return Environment(*args)
+ if env is not None:
+ return env
+ _spontaneous_environments[args] = env = Environment(*args)
+ return env
+
+
+def template_from_code(environment, code, globals, uptodate=None,
+ template_class=None):
+ """Generate a new template object from code. It's used in the
+ template constructor and the loader `load` implementation.
+ """
+ t = object.__new__(template_class or environment.template_class)
+ namespace = {
+ 'environment': environment,
+ '__jinja_template__': t
+ }
+ exec code in namespace
+ t.environment = environment
+ t.name = namespace['name']
+ t.filename = code.co_filename
+ t.root_render_func = namespace['root']
+ t.blocks = namespace['blocks']
+ t.globals = globals
+
+ # debug and loader helpers
+ t._debug_info = namespace['debug_info']
+ t._uptodate = uptodate
+
+ return t
+
+
class Environment(object):
"""The Jinja environment.
self.comment_end_string = comment_end_string
self.line_statement_prefix = line_statement_prefix
self.trim_blocks = trim_blocks
+
+ # load extensions
self.extensions = []
for extension in extensions:
if isinstance(extension, basestring):
extension = import_string(extension)
- self.extensions.append(extension(self))
+ # extensions are instanciated early but initalized later.
+ self.extensions.append(object.__new__(extension))
# runtime information
self.undefined = undefined
self.filters = DEFAULT_FILTERS.copy()
self.tests = DEFAULT_TESTS.copy()
self.globals = DEFAULT_NAMESPACE.copy()
- for extension in self.extensions:
- extension.update_globals(self.globals)
# set the loader provided
self.loader = loader
# create lexer
self.lexer = Lexer(self)
+ # initialize extensions
+ for extension in self.extensions:
+ extension.__init__(self)
+
def subscribe(self, obj, argument):
"""Get an item or attribute of an object."""
try:
globals = self.make_globals(globals)
return self.loader.load(self, name, globals)
- def from_string(self, source, globals=None):
+ def from_string(self, source, globals=None, template_class=None):
"""Load a template from a string."""
globals = self.make_globals(globals)
- return Template(self, self.compile(source, globals=globals),
- globals)
+ return template_from_code(self, self.compile(source, globals=globals),
+ globals, template_class)
def make_globals(self, d):
"""Return a dict for the globals."""
class Template(object):
- """Represents a template."""
-
- def __init__(self, environment, code, globals, uptodate=None):
- namespace = {
- 'environment': environment,
- '__jinja_template__': self
- }
- exec code in namespace
- self.environment = environment
- self.name = namespace['name']
- self.filename = code.co_filename
- self.root_render_func = namespace['root']
- self.blocks = namespace['blocks']
- self.globals = globals
-
- # debug and loader helpers
- self._get_debug_info = namespace['get_debug_info']
- self._uptodate = uptodate
+ """The central template object. This class represents a compiled template
+ and is used to evaluate it.
+
+ Normally the template object is generated from an `Environment` but it
+ also has a constructor that makes it possible to create a template
+ instance directly using the constructor. It takes the same arguments as
+ the environment constructor but it's not possible to specify a loader.
+
+ Every template object has a few methods and members that are guaranteed
+ to exist. However it's important that a template object should be
+ considered immutable. Modifications on the object are not supported.
+
+ Template objects created from the constructor rather than an environment
+ do have an `environment` attribute that points to a temporary environment
+ that is probably shared with other templates created with the constructor
+ and compatible settings.
+
+ >>> template = Template('Hello {{ name }}!')
+ >>> template.render(name='John Doe')
+ u'Hello John Doe!'
+
+ >>> stream = template.stream(name='John Doe')
+ >>> stream.next()
+ u'Hello John Doe!'
+ >>> stream.next()
+ Traceback (most recent call last):
+ ...
+ StopIteration
+ """
+
+ def __new__(cls, source,
+ block_start_string='{%',
+ block_end_string='%}',
+ variable_start_string='{{',
+ variable_end_string='}}',
+ comment_start_string='{#',
+ comment_end_string='#}',
+ line_statement_prefix=None,
+ trim_blocks=False,
+ optimized=True,
+ undefined=Undefined,
+ extensions=(),
+ finalize=unicode):
+ # make sure extensions are hashable
+ extensions = tuple(extensions)
+ env = _get_spontaneous_environment(
+ block_start_string, block_end_string, variable_start_string,
+ variable_end_string, comment_start_string, comment_end_string,
+ line_statement_prefix, trim_blocks, optimized, undefined,
+ None, extensions, finalize)
+ return env.from_string(source, template_class=cls)
def render(self, *args, **kwargs):
"""Render the template into a string."""
'With an enabled optimizer this '
'will lead to unexpected results.' %
(plural, ', '.join(overrides), plural or ' a', plural))
- gen = self.root_render_func(dict(self.globals, **context))
- # skip the first item which is a reference to the context
- gen.next()
try:
- for event in gen:
+ for event in self.root_render_func(self.new_context(context)):
yield event
except:
- exc_info = translate_exception(sys.exc_info())
- raise exc_info[0], exc_info[1], exc_info[2]
+ exc_type, exc_value, tb = translate_exception(sys.exc_info())
+ raise exc_type, exc_value, tb
+
+ def new_context(self, vars):
+ """Create a new template context for this template."""
+ return TemplateContext(self.environment, dict(self.globals, **vars),
+ self.name, self.blocks)
def get_corresponding_lineno(self, lineno):
"""Return the source line number of a line number in the
generated bytecode as they are not in sync.
"""
- for template_line, code_line in reversed(self._get_debug_info()):
+ for template_line, code_line in reversed(self.debug_info):
if code_line <= lineno:
return template_line
return 1
@property
def is_up_to_date(self):
- """Check if the template is still up to date."""
+ """If this variable is `False` there is a newer version available."""
if self._uptodate is None:
return True
return self._uptodate()
+ @property
+ def debug_info(self):
+ """The debug info mapping."""
+ return [tuple(map(int, x.split('='))) for x in
+ self._debug_info.split('&')]
+
def __repr__(self):
return '<%s %r>' % (
self.__class__.__name__,
- self.name
+ self.name or '<from string>'
)
class TemplateStream(object):
- """Wraps a genererator for outputing template streams."""
+ """This class wraps a generator returned from `Template.generate` so that
+ it's possible to buffer multiple elements so that it's possible to return
+ them from a WSGI application which flushes after each iteration.
+ """
def __init__(self, gen):
self._gen = gen
"""Enable buffering. Buffer `size` items before yielding them."""
if size <= 1:
raise ValueError('buffer size too small')
- self.buffered = True
- def buffering_next():
+ def generator():
buf = []
c_size = 0
push = buf.append
next = self._gen.next
- try:
- while 1:
- item = next()
- if item:
- push(item)
+ while 1:
+ try:
+ while 1:
+ push(next())
c_size += 1
- if c_size >= size:
- raise StopIteration()
- except StopIteration:
- if not c_size:
- raise
- return u''.join(buf)
+ if c_size >= size:
+ raise StopIteration()
+ except StopIteration:
+ if not c_size:
+ raise
+ yield u''.join(buf)
+ del buf[:]
+ c_size = 0
- self._next = buffering_next
+ self.buffered = True
+ self._next = generator().next
def __iter__(self):
return self
def next(self):
return self._next()
+
+
+# hook in default template class. if anyone reads this comment: ignore that
+# it's possible to use custom templates ;-)
+Environment.template_class = Template
def __init__(self, environment):
self.environment = environment
- def update_globals(self, globals):
- """Called to inject runtime variables into the globals."""
- pass
-
def parse(self, parser):
"""Called if one of the tags matched."""
"""An example extension that adds cacheable blocks."""
tags = set(['cache'])
+ def __init__(self, environment):
+ Extension.__init__(self, environment)
+ def dummy_cache_support(name, timeout=None, caller=None):
+ if caller is not None:
+ return caller()
+ environment.globals['cache_support'] = dummy_cache_support
+
def parse(self, parser):
lineno = parser.stream.next().lineno
args = [parser.parse_expression()]
class TransExtension(Extension):
tags = set(['trans'])
- def update_globals(self, globals):
- """Inject noop translation functions."""
- globals.update({
+ def __init__(self, environment):
+ Extension.__init__(self, environment)
+ environment.globals.update({
'_': lambda x: x,
'gettext': lambda x: x,
'ngettext': lambda s, p, n: (s, p)[n != 1]
"""
def __call__(cls, environment):
- key = hash((environment.block_start_string,
- environment.block_end_string,
- environment.variable_start_string,
- environment.variable_end_string,
- environment.comment_start_string,
- environment.comment_end_string,
- environment.line_statement_prefix,
- environment.trim_blocks))
+ key = (environment.block_start_string,
+ environment.block_end_string,
+ environment.variable_start_string,
+ environment.variable_end_string,
+ environment.comment_start_string,
+ environment.comment_end_string,
+ environment.line_statement_prefix,
+ environment.trim_blocks)
# use the cached lexer if possible
if key in _lexer_cache:
"""
from os import path
from jinja2.exceptions import TemplateNotFound
-from jinja2.environment import Template
+from jinja2.environment import template_from_code
from jinja2.utils import LRUCache
class BaseLoader(object):
- """
- Baseclass for all loaders. Subclass this and override `get_source` to
+ """Baseclass for all loaders. Subclass this and override `get_source` to
implement a custom loading mechanism.
The environment provides a `get_template` method that will automatically
source, filename, uptodate = self.get_source(environment, name)
code = environment.compile(source, name, filename, globals)
- template = Template(environment, code, globals, uptodate)
+ template = template_from_code(environment, code, globals, uptodate)
if self.cache is not None:
self.cache[name] = template
return template
if template in self.mapping:
return self.mapping[template], None, None
raise TemplateNotFound(template)
+
+
+class FunctionLoader(BaseLoader):
+ """A loader that is passed a function which does the loading. The
+ function has to work like a `get_source` method but the return value for
+ not existing templates may be `None` instead of a `TemplateNotFound`
+ exception.
+ """
+
+ def __init__(self, load_func, cache_size=50, auto_reload=True):
+ BaseLoader.__init__(self, cache_size, auto_reload)
+ self.load_func = load_func
+
+ def get_source(self, environment, template):
+ rv = self.load_func(environment, template)
+ if rv is None:
+ raise TemplateNotFound(template)
+ return rv
+
+
+class PrefixLoader(BaseLoader):
+ """A loader that is passed a dict of loaders where each loader is bound
+ to a prefix. The caching is independent of the actual loaders so the
+ per loader cache settings are ignored. The prefix is delimited from the
+ template by a slash.
+ """
+
+ def __init__(self, mapping, delimiter='/', cache_size=50,
+ auto_reload=True):
+ BaseLoader.__init__(self, cache_size, auto_reload)
+ self.mapping = mapping
+ self.delimiter = delimiter
+
+ def get_source(self, environment, template):
+ try:
+ prefix, template = template.split(self.delimiter, 1)
+ loader = self.mapping[prefix]
+ except (ValueError, KeyError):
+ raise TemplateNotFound(template)
+ return loader.get_source(environment, template)
+
+
+class ChoiceLoader(BaseLoader):
+ """This loader works like the `PrefixLoader` just that no prefix is
+ specified. If a template could not be found by one loader the next one
+ is tried. Like for the `PrefixLoader` the cache settings of the actual
+ loaders don't matter as the choice loader does the caching.
+ """
+
+ def __init__(self, loaders, cache_size=50, auto_reload=True):
+ BaseLoader.__init__(self, cache_size, auto_reload)
+ self.loaders = loaders
+
+ def get_source(self, environment, template):
+ for loader in self.loaders:
+ try:
+ return loader.get_source(environment, template)
+ except TemplateNotFound:
+ pass
+ raise TemplateNotFound(template)
obj = self.node.as_const()
# don't evaluate context functions
- if type(obj) is FunctionType and \
- getattr(obj, 'contextfunction', False):
- raise Impossible()
-
args = [x.as_const() for x in self.args]
+ if type(obj) is FunctionType:
+ if getattr(obj, 'contextfunction', False):
+ raise Impossible()
+ elif obj.environmentfunction:
+ args.insert(0, self.environment)
+
kwargs = dict(x.as_const() for x in self.kwargs)
if self.dyn_args is not None:
try:
:copyright: Copyright 2008 by Armin Ronacher.
:license: GNU GPL.
"""
-try:
- from collections import defaultdict
-except ImportError:
- defaultdict = None
from types import FunctionType
from jinja2.utils import Markup, partial
from jinja2.exceptions import UndefinedError
'Macro', 'IncludedTemplate', 'Markup']
-class TemplateContext(dict):
+class TemplateContext(object):
"""Holds the variables of the local template or of the global one. It's
not save to use this class outside of the compiled code. For example
update and other methods will not work as they seem (they don't update
the exported variables for example).
"""
- def __init__(self, environment, globals, name, blocks, standalone):
- dict.__init__(self, globals)
+ def __init__(self, environment, parent, name, blocks):
+ self.parent = parent
+ self.vars = {}
self.environment = environment
- self.exported = set()
+ self.exported_vars = set()
self.name = name
+
+ # bind functions to the context of environment if required
+ for name, obj in self.parent.iteritems():
+ if type(obj) is FunctionType:
+ if getattr(obj, 'contextfunction', 0):
+ self.vars[key] = partial(obj, self)
+ elif getattr(obj, 'environmentfunction', 0):
+ self.vars[key] = partial(obj, environment)
+
+ # create the initial mapping of blocks. Whenever template inheritance
+ # takes place the runtime will update this mapping with the new blocks
+ # from the template.
self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
- # give all context functions the context as first argument
- for key, value in self.iteritems():
- if type(value) is FunctionType and \
- getattr(value, 'contextfunction', False):
- dict.__setitem__(self, key, partial(value, self))
-
- # if the template is in standalone mode we don't copy the blocks over.
- # this is used for includes for example but otherwise, if the globals
- # are a template context, this template is participating in a template
- # inheritance chain and we have to copy the blocks over.
- if not standalone and isinstance(globals, TemplateContext):
- for name, parent_blocks in globals.blocks.iteritems():
- self.blocks.setdefault(name, []).extend(parent_blocks)
-
- def super(self, block):
+ def super(self, name, current):
"""Render a parent block."""
- try:
- func = self.blocks[block][-2]
- except LookupError:
+ last = None
+ for block in self.blocks[name]:
+ if block is current:
+ break
+ last = block
+ if last is None:
return self.environment.undefined('there is no parent block '
'called %r.' % block)
- return SuperBlock(block, self, func)
+ return SuperBlock(block, self, last)
- def __setitem__(self, key, value):
- """If we set items to the dict we track the variables set so
- that includes can access the exported variables."""
- dict.__setitem__(self, key, value)
- self.exported.add(key)
+ def update(self, mapping):
+ """Update vars from a mapping but don't export them."""
+ self.vars.update(mapping)
def get_exported(self):
- """Get a dict of all exported variables."""
- return dict((k, self[k]) for k in self.exported)
-
- # if there is a default dict, dict has a __missing__ method we can use.
- if defaultdict is None:
- def __getitem__(self, name):
- if name in self:
- return self[name]
- return self.environment.undefined(name=name)
- else:
- def __missing__(self, name):
- return self.environment.undefined(name=name)
+ """Get a new dict with the exported variables."""
+ return dict((k, self.vars[k]) for k in self.exported_vars)
+
+ def get_root(self):
+ """Return a new dict with all the non local variables."""
+ return dict(self.parent)
+
+ def get_all(self):
+ """Return a copy of the complete context as dict."""
+ return dict(self.parent, **self.vars)
+
+ def __setitem__(self, key, value):
+ self.vars[key] = value
+ self.exported_vars.add(key)
+
+ def __getitem__(self, key):
+ if key in self.vars:
+ return self.vars[key]
+ try:
+ return self.parent[key]
+ except KeyError:
+ return self.environment.undefined(name=key)
def __repr__(self):
return '<%s %s of %r>' % (
def __init__(self, environment, context, template):
template = environment.get_template(template)
- gen = template.root_render_func(context, standalone=True)
- context = gen.next()
+ context = template.new_context(context.get_root())
self._name = template.name
- self._rendered_body = u''.join(gen)
+ self._rendered_body = u''.join(template.root_render_func(context))
self._context = context.get_exported()
__getitem__ = lambda x, n: x._context[n]
)
+def fail_with_undefined_error(self, *args, **kwargs):
+ """Regular callback function for undefined objects that raises an
+ `UndefinedError` on call.
+ """
+ if self._undefined_hint is None:
+ if self._undefined_obj is None:
+ hint = '%r is undefined' % self._undefined_name
+ elif not isinstance(self._undefined_name, basestring):
+ hint = '%r object has no element %r' % (
+ self._undefined_obj.__class__.__name__,
+ self._undefined_name
+ )
+ else:
+ hint = '%r object has no attribute %r' % (
+ self._undefined_obj.__class__.__name__,
+ self._undefined_name
+ )
+ else:
+ hint = self._undefined_hint
+ raise UndefinedError(hint)
+
+
class Undefined(object):
"""The default undefined implementation. This undefined implementation
can be printed and iterated over, but every other access will raise a
self._undefined_obj = obj
self._undefined_name = name
- def _fail_with_error(self, *args, **kwargs):
- if self._undefined_hint is None:
- if self._undefined_obj is None:
- hint = '%r is undefined' % self._undefined_name
- elif not isinstance(self._undefined_name, basestring):
- hint = '%r object has no element %r' % (
- self._undefined_obj.__class__.__name__,
- self._undefined_name
- )
- else:
- hint = '%r object has no attribute %r' % (
- self._undefined_obj.__class__.__name__,
- self._undefined_name
- )
- else:
- hint = self._undefined_hint
- raise UndefinedError(hint)
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
__realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
- __getattr__ = __getitem__ = _fail_with_error
-
- def __unicode__(self):
- return u''
+ __getattr__ = __getitem__ = fail_with_undefined_error
def __str__(self):
return self.__unicode__().encode('utf-8')
def __repr__(self):
return 'Undefined'
+ def __unicode__(self):
+ return u''
+
def __len__(self):
return 0
class StrictUndefined(Undefined):
- """An undefined that barks on print and iteration as well as boolean tests.
- In other words: you can do nothing with it except checking if it's defined
- using the `defined` test.
+ """An undefined that barks on print and iteration as well as boolean
+ tests. In other words: you can do nothing with it except checking if it's
+ defined using the `defined` test.
"""
- __iter__ = __unicode__ = __len__ = __nonzero__ = Undefined._fail_with_error
+ __iter__ = __unicode__ = __len__ = __nonzero__ = fail_with_undefined_error
return f
+def environmentfunction(f):
+ """Mark a callable as environment callable. An environment callable is
+ passed the current environment as first argument.
+ """
+ f.environmentfunction = True
+ return f
+
+
def import_string(import_name, silent=False):
"""Imports an object based on a string. This use useful if you want to
use import paths as endpoints or something similar. An import path can
# -*- 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
-executable python code. It's basically a combination of Django templates
-and python code.
+Jinja2 is a template engine written in pure Python. It provides a
+`Django`_ inspired non-XML syntax but supports inline expressions and
+an optional `sandboxed`_ environment.
Nutshell
--------
{% block content %}
<ul>
{% for user in users %}
- <li><a href="{{ user.url|e }}">{{ user.username|e }}</a></li>
+ <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Application logic is for the controller but don't try to make the life
for the template designer too hard by giving him too few functionality.
-For more informations visit the new `jinja webpage`_ and `documentation`_.
+For more informations visit the new `jinja2 webpage`_ and `documentation`_.
-Note
-----
-
-This is the Jinja 1.0 release which is completely incompatible with the
-old "pre 1.0" branch. The old branch will still receive security updates
-and bugfixes but the 1.0 branch will be the only version that receives
-support.
-
-If you have an application that uses Jinja 0.9 and won't be updated in
-the near future the best idea is to ship a Jinja 0.9 checkout together
-with the application.
-
-The `Jinja tip`_ is installable via `easy_install` with ``easy_install
-Jinja==dev``.
+The `Jinja2 tip`_ is installable via `easy_install` with ``easy_install
+Jinja2==dev``.
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
.. _Django: http://www.djangoproject.com/
-.. _jinja webpage: http://jinja.pocoo.org/
-.. _documentation: http://jinja.pocoo.org/documentation/index.html
-.. _Jinja tip: http://dev.pocoo.org/hg/jinja-main/archive/tip.tar.gz#egg=Jinja-dev
+.. _jinja webpage: http://jinja2.pocoo.org/
+.. _documentation: http://jinja2.pocoo.org/documentation/index.html
+.. _Jinja tip: http://dev.pocoo.org/hg/jinja2-main/archive/tip.tar.gz#egg=Jinja2-dev
"""
import os
import sys
yield fn
+def get_terminal_width():
+ """Return the current terminal dimensions."""
+ try:
+ from struct import pack, unpack
+ from fcntl import ioctl
+ from termios import TIOCGWINSZ
+ s = pack('HHHH', 0, 0, 0, 0)
+ return unpack('HHHH', ioctl(sys.stdout.fileno(), TIOCGWINSZ, s))[1]
+ except:
+ return 80
+
+
class optional_build_ext(build_ext):
"""This class allows C extension building to fail."""
self._unavailable()
def _unavailable(self):
- print '*' * 70
+ width = get_terminal_width()
+ print '*' * width
print """WARNING:
An optional C extension could not be compiled, speedups will not be
available."""
- print '*' * 70
+ print '*' * width
setup(
- name='Jinja 2',
+ name='Jinja2',
version='2.0dev',
url='http://jinja.pocoo.org/',
license='BSD',
CONCAT = '''{{ [1, 2] ~ 'foo' }}'''
COMPARE = '''{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|{{ 2 == 2 }}|{{ 1 <= 1 }}'''
INOP = '''{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}'''
-LITERALS = '''{{ [] }}|{{ {} }}|{{ () }}|{{ '' }}|{{ @() }}'''
+LITERALS = '''{{ [] }}|{{ {} }}|{{ () }}'''
BOOL = '''{{ true and false }}|{{ false or true }}|{{ not false }}'''
GROUPING = '''{{ (true and false) or (false and true) and not false }}'''
CONDEXPR = '''{{ 0 if true else 1 }}'''
DJANGOATTR = '''{{ [1, 2, 3].0 }}'''
FILTERPRIORITY = '''{{ "foo"|upper + "bar"|upper }}'''
-REGEX = r'''{{ @/\S+/.findall('foo bar baz') }}'''
TUPLETEMPLATES = [
'{{ () }}',
'{{ (1, 2) }}',
'{% for x in foo, bar recursive %}...{% endfor %}',
'{% for x, in foo, recursive %}...{% endfor %}'
]
-TRAILINGCOMMA = '''{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}|{{ @(1, 2,) }}'''
+TRAILINGCOMMA = '''{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}'''
def test_call():
def test_literals(env):
tmpl = env.from_string(LITERALS)
- assert tmpl.render().lower() == '[]|{}|()||set([])'
+ assert tmpl.render().lower() == '[]|{}|()'
def test_bool(env):
env.from_string('foo(%s)' % sig)
-def test_regex(env):
- tmpl = env.from_string(REGEX)
- assert tmpl.render() == "['foo', 'bar', 'baz']"
-
-
def test_tuple_expr(env):
for tmpl in TUPLETEMPLATES:
assert env.from_string(tmpl)
def test_trailing_comma(env):
tmpl = env.from_string(TRAILINGCOMMA)
- assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}|set([1, 2])'
-
-
-def test_extends_position():
- env = Environment(loader=DictLoader({
- 'empty': '[{% block empty %}{% endblock %}]'
- }))
- tests = [
- ('{% extends "empty" %}', '[!]'),
- (' {% extends "empty" %}', '[!]'),
- (' !\n', ' !\n!'),
- ('{# foo #} {% extends "empty" %}', '[!]'),
- ('{% set foo = "blub" %}{% extends "empty" %}', None)
- ]
-
- for tmpl, expected_output in tests:
- try:
- tmpl = env.from_string(tmpl + '{% block empty %}!{% endblock %}')
- except TemplateSyntaxError:
- assert expected_output is None, 'got syntax error'
- else:
- assert expected_output == tmpl.render()
+ assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'