Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #3 from axiak/master

less boilerplate.
  • Loading branch information...
commit 44869f5a0023ecaf0318b28a3e0cf83340a7021b 2 parents 3eb1456 + ea35020
@mankyd authored
View
2  AUTHORS
@@ -1 +1,3 @@
David Mankoff <mankyd@gmail.com>
+
+Mike Axiak <mike@axiak.net>
View
178 jinjatag/decorators.py
@@ -1,3 +1,7 @@
+import sys
+import inspect
+import traceback
+
try:
import plocal
except ImportError:
@@ -7,59 +11,126 @@
from jinja2.ext import Extension
from jinja2.lexer import Token
+
import extension
__all__ = ('simple_tag', 'simple_block', 'multibody_block',)
-def simple_tag(name=None):
- def dec(func):
- tag_name = name if isinstance(name, basestring) else func.__name__
- cls = type(tag_name, (extension._SimpleTagExt,), {})
- cls.tags = {tag_name}
- cls.tag_func = staticmethod(func)
- extension._jinja_tags.add_tag_ext(cls)
- return func
- if callable(name):
- return dec(name)
- return dec
-
-def simple_block(name=None):
- def dec(func):
- tag_name = name if isinstance(name, basestring) else func.__name__
- cls = type(tag_name, (extension._SimpleBlockExt,), {})
- cls.tags = {tag_name}
- cls.tag_func = staticmethod(func)
- extension._jinja_tags.add_tag_ext(cls)
- return func
- if callable(name):
- return dec(name)
- return dec
-
-def multibody_block(name=None):
- def dec(func):
- tag_name = name if isinstance(name, basestring) else func.__name__
- cls = type(tag_name, (_MultiBodyBlockExt,), {
- 'tags': {tag_name},
- 'tag_func': staticmethod(func)})
- extension._jinja_tags.add_tag_ext(cls)
- return func
- if callable(name):
- return dec(name)
- return dec
-
-class _MultiBodyBlockExt(Extension):
+def create_extension_decorator(cls):
+ """
+ Class decorator that turns a class representing an Extension
+ into a decorator of the same name that registers the given class
+ with the jinja_tags registration.
+ It also adds 4 instance variables:
+
+ - tags - The set of the tag_name
+ - tag_func - The function to dispatch to (wrapped by the decorator)
+ - decorator_args - Any extra args passed to the decorator
+ - decorator_kwargs - Any extra kwargs passed to the decorator
+ """
+ def outer_dec(name=None, *args, **kwargs):
+ def dec(func):
+ tag_name = name if isinstance(name, basestring) and name else func.__name__
+ new_cls = type(tag_name, (cls,), {
+ 'tags': set([tag_name]),
+ 'tag_func': staticmethod(func),
+ 'decorator_args': args,
+ 'decorator_kwargs': kwargs,
+ })
+ extension._jinja_tags.add_tag_ext(new_cls)
+ return func
+ if callable(name):
+ return dec(name)
+ return dec
+ return outer_dec
+
+
+class BaseTag(Extension):
+ def parse_attrs(self, parser, add_id=True):
+ attrs = {}
+ while parser.stream.current.type != 'block_end':
+ node = parser.parse_assign_target(with_tuple=False)
+
+ if parser.stream.skip_if('assign'):
+ attrs[node.name] = parser.parse_expression()
+ else:
+ attrs[node.name] = nodes.Const(node.name)
+
+ return attrs
+
+
+ def call_tag_func(self, *args, **kwargs):
+ try:
+ return self.tag_func(*args, **kwargs)
+ except TypeError as e:
+ t, value, tb = sys.exc_info()
+ if len(traceback.extract_tb(tb, 2)) > 1:
+ raise
+ argspec = inspect.getargspec(self.tag_func)
+ arg_list = list(argspec.args)
+ if argspec.varargs:
+ arg_list.append('*' + argspec.varargs)
+ if argspec.keywords:
+ arg_list.append('**' + argspec.keywords)
+ raise TypeError("Failed to satisfy arguments for {}({}): provided ({}).".format(
+ iter(self.tags).next(),
+ ', '.join(arg_list),
+ ', '.join([str(arg) for arg in args] + ['{}={}'.format(k, repr(v)) for k, v in kwargs.items()])))
+
+@create_extension_decorator
+class simple_tag(BaseTag):
def parse(self, parser):
tag = parser.stream.next()
- end_tags = ['name:end' + tag.value,
- 'name:end_' + tag.value,
- 'name:{}_block'.format(tag.value),
- 'name:{}_endblock'.format(tag.value),
- 'name:{}_end_block'.format(tag.value),
- ]
+ attrs = self.parse_attrs(parser)
+ attrs = nodes.Dict([nodes.Pair(nodes.Const(k), v) for k,v in attrs.items()])
+
+ return nodes.Output([self.call_method('_call_simple_tag', args=[attrs])])
- attrs_ = extension.JinjaTag._parse_attrs(parser)
- body = parser.parse_statements(end_tags, drop_needle=False)
+ def _call_simple_tag(self, attrs):
+ return self.call_tag_func(**attrs)
+
+@create_extension_decorator
+class simple_block(BaseTag):
+ def parse(self, parser):
+ tag = parser.stream.next()
+
+ attrs = self.parse_attrs(parser)
+ attrs = nodes.Dict([nodes.Pair(nodes.Const(k), v) for k,v in attrs.items()])
+
+ body = parser.parse_statements(['name:end'+tag.value], drop_needle=True)
+
+ return [nodes.CallBlock(self.call_method('_call_simple_block', args=[attrs]),
+ [], [], body).set_lineno(tag.lineno)]
+
+ def _call_simple_block(self, attrs, caller):
+ return self.call_tag_func(caller(), **attrs)
+
+
+@create_extension_decorator
+class multibody_block(BaseTag):
+ def parse(self, parser):
+ INSIDE_BLOCK, OUTSIDE_BLOCK = 0, 1
+
+ tag = parser.stream.next()
+
+ end_tags_in_block = [
+ 'name:{}_endblock'.format(tag.value),
+ 'name:{}_end_block'.format(tag.value),
+ ]
+
+ end_tags_outside_block = [
+ 'name:end' + tag.value,
+ 'name:end_' + tag.value,
+ 'name:{}_block'.format(tag.value),
+ ]
+
+ end_tags = (end_tags_in_block, end_tags_outside_block)
+
+ state = OUTSIDE_BLOCK
+
+ attrs_ = self.parse_attrs(parser)
+ body = parser.parse_statements(end_tags[state], drop_needle=False)
node_list = []
@@ -67,22 +138,27 @@ def parse(self, parser):
('body', body, tag.lineno),
]
+
while True:
sub_tag = parser.stream.next()
sub_tag_name = sub_tag.value
- tag_index = end_tags.index('name:' + sub_tag_name)
+ tag_index = end_tags[state].index('name:' + sub_tag_name)
- if tag_index < 2:
+ if state == OUTSIDE_BLOCK and tag_index < 2:
break
- elif tag_index == 2:
+ elif state == OUTSIDE_BLOCK:
+ # entering new block
sub_block_name = parser.stream.next().value
- body = parser.parse_statements(end_tags, drop_needle=False)
+ state = INSIDE_BLOCK
+ body = parser.parse_statements(end_tags[state], drop_needle=False)
blocks.append((sub_block_name, body, sub_tag.lineno))
else:
- parser.parse_statements(end_tags, drop_needle=False)
+ state = OUTSIDE_BLOCK
+ parser.parse_statements(end_tags[state], drop_needle=False)
+
self.block_results = plocal.local()
self.block_results.data = {}
@@ -107,7 +183,7 @@ def _call_multiblock_tag(self, attrs):
block_results = self.block_results.data
self.block_results.data = {}
attrs.update(block_results)
- return self.tag_func(**attrs)
+ return self.call_tag_func(**attrs)
@classmethod
def to_node_dict(cls, d):
View
42 jinjatag/extension.py
@@ -1,35 +1,8 @@
-from jinja2 import Environment, environmentfunction, nodes
+from jinja2 import Environment
from jinja2.ext import Extension
__all__ = ('TagRegistrar', 'JinjaTag',)
-class _SimpleTagExt(Extension):
- def parse(self, parser):
- tag = parser.stream.next()
-
- attrs = JinjaTag._parse_attrs(parser)
- attrs = nodes.Dict([nodes.Pair(nodes.Const(k), v) for k,v in attrs.items()])
-
- return nodes.Output([self.call_method('_call_simple_tag', args=[attrs])])
-
- def _call_simple_tag(self, attrs):
- return self.tag_func(**attrs)
-
-class _SimpleBlockExt(Extension):
- def parse(self, parser):
- tag = parser.stream.next()
-
- attrs = JinjaTag._parse_attrs(parser)
- attrs = nodes.Dict([nodes.Pair(nodes.Const(k), v) for k,v in attrs.items()])
-
- body = parser.parse_statements(['name:end'+tag.value], drop_needle=True)
-
- return [nodes.CallBlock(self.call_method('_call_simple_block', args=[attrs]),
- [], [], body).set_lineno(tag.lineno)]
-
- def _call_simple_block(self, attrs, caller):
- return self.tag_func(caller(), **attrs)
-
class JinjaTag(Extension):
def __init__(self):
pass
@@ -42,19 +15,6 @@ def __call__(self, environment):
def init(self):
_jinja_tags.set_base_ext(self)
- @classmethod
- def _parse_attrs(cls, parser, add_id=True):
- attrs = {}
- while parser.stream.current.type != 'block_end':
- node = parser.parse_assign_target(with_tuple=False)
-
- if parser.stream.skip_if('assign'):
- attrs[node.name] = parser.parse_expression()
- else:
- attrs[node.name] = nodes.Const(node.name)
-
- return attrs
-
class TagRegistrar(object):
def __init__(self):
self.ext = None
View
48 jinjatag/tests/multiblock.py
@@ -1,5 +1,7 @@
import unittest
+from jinja2 import TemplateSyntaxError
+
import jinjatag
from jinjatag.tests import JinjaTagTestCase
@@ -28,6 +30,52 @@ def test_mbb_simple(self):
'''.strip())
self.assertEquals(tmpl.render(), '<h1>\n this is the header\n </h1>x=foo, y=bar<br> \n\n this is the random body\n\n <footer>\n this is the footer\n </footer>')
+ def test_mbb_simple_errors(self):
+ # for loop outside inner block
+ tmpl1_string = '''
+{% mbb_simple x="foo" y="bar" %}
+
+ this is the random body
+
+ {% mbb_simple_block 'header' %}
+ this is the header
+ {% mbb_simple_endblock %}
+ {% for i in range(10) %}
+ {% mbb_simple_block i %}
+ {% mbb_simple_endblock %}
+ {% endfor %}
+{% end_mbb_simple %}
+'''
+ self.assertRaises(TemplateSyntaxError, self.env.from_string, tmpl1_string)
+
+ tmpl2_string = '''
+{% mbb_simple x="foo" y="bar" %}
+
+body
+
+{% mbb_simple_endblock %}
+{% end_mbb_simple %}
+'''
+ self.assertRaises(TemplateSyntaxError, self.env.from_string, tmpl2_string)
+
+ @jinjatag.multibody_block
+ def mbb_simple_bam(body, header='', footer='', x=None, y=None):
+ raise ValueError(header)
+
+
+ def test_func_exception(self):
+ self.assertRaises(ValueError,
+ self.env.from_string(''' {% mbb_simple_bam x=10 y=20 %} {% end_mbb_simple_bam %} ''').render)
+
+ @jinjatag.multibody_block
+ def mbb_missing_param(body, header):
+ return str(header)
+
+ def test_missing_param(self):
+ self.assertRaises(TypeError,
+ self.env.from_string('{% mbb_missing_param %} {% end_mbb_missing_param %}').render)
+
+
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(MultiBlockTagTestCase))
View
1  setup.py
@@ -25,5 +25,6 @@
"License :: OSI Approved :: GNU General Public License (GPL)",
"Operating System :: OS Independent",
"Programming Language :: Python",
+ "Programming Language :: Python :: 2.7",
]
)
Please sign in to comment.
Something went wrong with that request. Please try again.