Skip to content

Commit

Permalink
moved IncludedTemplate into the regular template API, fixed more un…
Browse files Browse the repository at this point in the history
…ittests

--HG--
branch : trunk
  • Loading branch information
mitsuhiko committed Apr 25, 2008
1 parent b5124e6 commit 963f97d
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 156 deletions.
31 changes: 20 additions & 11 deletions jinja2/compiler.py
Expand Up @@ -441,14 +441,19 @@ def function_scoping(self, node, frame):
func_frame.identifiers.declared
)

func_frame.accesses_arguments = False
func_frame.accesses_kwargs = False
func_frame.accesses_varargs = False
func_frame.accesses_caller = False
func_frame.arguments = args = ['l_' + x.name for x in node.args]

if 'arguments' in func_frame.identifiers.undeclared:
func_frame.accesses_arguments = True
func_frame.identifiers.add_special('arguments')
args.append('l_arguments')
if 'kwargs' in func_frame.identifiers.undeclared:
func_frame.accesses_kwargs = True
func_frame.identifiers.add_special('kwargs')
args.append('l_kwargs')
if 'varargs' in func_frame.identifiers.undeclared:
func_frame.accesses_varargs = True
func_frame.identifiers.add_special('varargs')
args.append('l_varargs')
if 'caller' in func_frame.identifiers.undeclared:
func_frame.accesses_caller = True
func_frame.identifiers.add_special('caller')
Expand Down Expand Up @@ -598,14 +603,14 @@ def visit_Include(self, node, frame):
self.writeline('l_%s = ' % node.target, node)
if frame.toplevel:
self.write('context[%r] = ' % node.target)
self.write('IncludedTemplate(environment, context, ')
self.write('environment.get_template(')
self.visit(node.template, frame)
self.write(')')
self.write(', %r).include(context)' % self.name)
return

self.writeline('included_template = environment.get_template(', node)
self.visit(node.template, frame)
self.write(')')
self.write(', %r)' % self.name)
if frame.toplevel:
self.writeline('included_context = included_template.new_context('
'context.get_root())')
Expand Down Expand Up @@ -729,8 +734,9 @@ def visit_Macro(self, node, frame):
for arg in node.defaults:
self.visit(arg, macro_frame)
self.write(', ')
self.write('), %s, %s)' % (
macro_frame.accesses_arguments and '1' or '0',
self.write('), %s, %s, %s)' % (
macro_frame.accesses_kwargs and '1' or '0',
macro_frame.accesses_varargs and '1' or '0',
macro_frame.accesses_caller and '1' or '0'
))

Expand All @@ -753,7 +759,10 @@ def visit_CallBlock(self, node, frame):
for arg in node.defaults:
self.visit(arg)
self.write(', ')
self.write('), %s, 0)' % (call_frame.accesses_arguments and '1' or '0'))
self.write('), %s, %s, 0)' % (
call_frame.accesses_kwargs and '1' or '0',
call_frame.accesses_varargs and '1' or '0'
))
if frame.buffer is None:
self.writeline('yield ', node)
else:
Expand Down
29 changes: 29 additions & 0 deletions jinja2/environment.py
Expand Up @@ -347,6 +347,16 @@ def new_context(self, vars):
return TemplateContext(self.environment, dict(self.globals, **vars),
self.name, self.blocks)

def include(self, context=None):
"""Include this template."""
if context is None:
context = self.new_context({})
elif isinstance(context, TemplateContext):
context = self.new_context(context.get_root())
else:
context = self.new_context(context)
return IncludedTemplate(self, context)

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.
Expand Down Expand Up @@ -376,6 +386,25 @@ def __repr__(self):
)


class IncludedTemplate(object):
"""Represents an included template."""

def __init__(self, template, context):
self._template = template
self._name = template.name
self._rendered_body = u''.join(template.root_render_func(context))
self._context = context.get_exported()

__getitem__ = lambda x, n: x._context[n]
__html__ = __unicode__ = lambda x: x._rendered_body

def __repr__(self):
return '<%s %r>' % (
self.__class__.__name__,
self._name
)


class TemplateStream(object):
"""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
Expand Down
26 changes: 21 additions & 5 deletions jinja2/filters.py
Expand Up @@ -378,10 +378,12 @@ def do_wordwrap(s, pos=79, hard=False):
len(word.split('\n', 1)[0]) >= pos)],
word), s.split(' '))


def do_wordcount(s):
"""Count the words in that string."""
return len(s.split())


def do_int(value, default=0):
"""Convert the value into an integer. If the
conversion doesn't work it will return ``0``. You can
Expand Down Expand Up @@ -563,16 +565,30 @@ def do_groupby(environment, value, attribute):
{% endfor %}
</ul>
Additionally it's possible to use tuple unpacking for the grouper and
list:
.. sourcecode:: html+jinja
<ul>
{% for grouper, list in persons|groupby('gender') %}
...
{% endfor %}
</ul>
As you can see the item we're grouping by is stored in the `grouper`
attribute and the `list` contains all the objects that have this grouper
in common.
"""
expr = lambda x: environment.subscribe(x, attribute)
return sorted([{
'grouper': a,
'list': b
} for a, b in groupby(sorted(value, key=expr), expr)],
key=itemgetter('grouper'))
return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)),
key=itemgetter('grouper'))


class _GroupTuple(tuple):
__slots__ = ()
grouper = property(itemgetter(0))
list = property(itemgetter(1))


FILTERS = {
Expand Down
13 changes: 8 additions & 5 deletions jinja2/loaders.py
Expand Up @@ -126,7 +126,8 @@ def __init__(self, package_name, package_path, charset='utf-8',
self.package_path = package_path

def get_source(self, environment, template):
path = '/'.join(split_template_path(template))
pieces = split_template_path(template)
path = '/'.join((self.package_path,) + tuple(pieces))
if not self._pkg.resource_exists(self.package_name, path):
raise TemplateNotFound(template)
return self._pkg.resource_string(self.package_name, path), None, None
Expand All @@ -147,19 +148,21 @@ def get_source(self, environment, 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.
function becomes the name of the template passed and has to return either
an unicode string with the template source, a tuple in the form ``(source,
filename, uptodatefunc)`` or `None` if the template does not exist.
"""

def __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)
rv = self.load_func(template)
if rv is None:
raise TemplateNotFound(template)
elif isinstance(rv, basestring):
return rv, None, None
return rv


Expand Down
43 changes: 15 additions & 28 deletions jinja2/runtime.py
Expand Up @@ -14,7 +14,7 @@


__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
'Macro', 'IncludedTemplate', 'Markup']
'Macro', 'Markup']


class TemplateContext(object):
Expand Down Expand Up @@ -97,7 +97,7 @@ def __getitem__(self, key):
def __repr__(self):
return '<%s %s of %r>' % (
self.__class__.__name__,
dict.__repr__(self),
repr(self.get_all()),
self.name
)

Expand All @@ -120,26 +120,6 @@ def __repr__(self):
)


class IncludedTemplate(object):
"""Represents an included template."""

def __init__(self, environment, context, template):
template = environment.get_template(template)
context = template.new_context(context.get_root())
self._name = template.name
self._rendered_body = u''.join(template.root_render_func(context))
self._context = context.get_exported()

__getitem__ = lambda x, n: x._context[n]
__html__ = __unicode__ = lambda x: x._rendered_body

def __repr__(self):
return '<%s %r>' % (
self.__class__.__name__,
self._name
)


class LoopContextBase(object):
"""Helper for extended iteration."""

Expand Down Expand Up @@ -228,19 +208,21 @@ def make_static(self):
class Macro(object):
"""Wraps a macro."""

def __init__(self, environment, func, name, arguments, defaults, catch_all, caller):
def __init__(self, environment, func, name, arguments, defaults,
catch_kwargs, catch_varargs, caller):
self._environment = environment
self._func = func
self.name = name
self.arguments = arguments
self.defaults = defaults
self.catch_all = catch_all
self.catch_kwargs = catch_kwargs
self.catch_varargs = catch_varargs
self.caller = caller

def __call__(self, *args, **kwargs):
arg_count = len(self.arguments)
if len(args) > arg_count:
raise TypeError('macro %r takes not more than %d argument(s).' %
if not self.catch_varargs and len(args) > arg_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
arguments = {}
for idx, name in enumerate(self.arguments):
Expand All @@ -261,8 +243,13 @@ def __call__(self, *args, **kwargs):
if caller is None:
caller = self._environment.undefined('No caller defined')
arguments['l_caller'] = caller
if self.catch_all:
arguments['l_arguments'] = kwargs
if self.catch_kwargs:
arguments['l_kwargs'] = kwargs
elif kwargs:
raise TypeError('macro %r takes no keyword argument %r' %
(self.name, iter(kwargs).next()))
if self.catch_varargs:
arguments['l_varargs'] = args[arg_count:]
return self._func(**arguments)

def __repr__(self):
Expand Down
3 changes: 1 addition & 2 deletions jinja2/visitor.py
Expand Up @@ -24,8 +24,7 @@ class name of the node. So a `TryFinally` node visit function would
"""

def get_visitor(self, node):
"""
Return the visitor function for this node or `None` if no visitor
"""Return the visitor function for this node or `None` if no visitor
exists for this node. In that case the generic visit function is
used instead.
"""
Expand Down
1 change: 0 additions & 1 deletion tests/loaderres/templates/brokenimport.html

This file was deleted.

0 comments on commit 963f97d

Please sign in to comment.