allow history to work in webkit browsers
[gae-samples.git] / python27 / guestbook / jinja2 / testsuite / ext.py
blob6ca6c228f847947b0497e73b4012e6206712f3d7
1 # -*- coding: utf-8 -*-
2 """
3 jinja2.testsuite.ext
4 ~~~~~~~~~~~~~~~~~~~~
6 Tests for the extensions.
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
10 """
11 import re
12 import unittest
14 from jinja2.testsuite import JinjaTestCase
16 from jinja2 import Environment, DictLoader, contextfunction, nodes
17 from jinja2.exceptions import TemplateAssertionError
18 from jinja2.ext import Extension
19 from jinja2.lexer import Token, count_newlines
20 from jinja2.utils import next
22 # 2.x / 3.x
23 try:
24 from io import BytesIO
25 except ImportError:
26 from StringIO import StringIO as BytesIO
29 importable_object = 23
31 _gettext_re = re.compile(r'_\((.*?)\)(?s)')
34 i18n_templates = {
35 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
36 '{% block body %}{% endblock %}',
37 'child.html': '{% extends "master.html" %}{% block body %}'
38 '{% trans %}watch out{% endtrans %}{% endblock %}',
39 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
40 '{{ user_count }} users online{% endtrans %}',
41 'stringformat.html': '{{ _("User: %(num)s")|format(num=user_count) }}'
44 newstyle_i18n_templates = {
45 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
46 '{% block body %}{% endblock %}',
47 'child.html': '{% extends "master.html" %}{% block body %}'
48 '{% trans %}watch out{% endtrans %}{% endblock %}',
49 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
50 '{{ user_count }} users online{% endtrans %}',
51 'stringformat.html': '{{ _("User: %(num)s", num=user_count) }}',
52 'ngettext.html': '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
53 'ngettext_long.html': '{% trans num=apples %}{{ num }} apple{% pluralize %}'
54 '{{ num }} apples{% endtrans %}',
55 'transvars1.html': '{% trans %}User: {{ num }}{% endtrans %}',
56 'transvars2.html': '{% trans num=count %}User: {{ num }}{% endtrans %}',
57 'transvars3.html': '{% trans count=num %}User: {{ count }}{% endtrans %}',
58 'novars.html': '{% trans %}%(hello)s{% endtrans %}',
59 'vars.html': '{% trans %}{{ foo }}%(foo)s{% endtrans %}',
60 'explicitvars.html': '{% trans foo="42" %}%(foo)s{% endtrans %}'
64 languages = {
65 'de': {
66 'missing': u'fehlend',
67 'watch out': u'pass auf',
68 'One user online': u'Ein Benutzer online',
69 '%(user_count)s users online': u'%(user_count)s Benutzer online',
70 'User: %(num)s': u'Benutzer: %(num)s',
71 'User: %(count)s': u'Benutzer: %(count)s',
72 '%(num)s apple': u'%(num)s Apfel',
73 '%(num)s apples': u'%(num)s Äpfel'
78 @contextfunction
79 def gettext(context, string):
80 language = context.get('LANGUAGE', 'en')
81 return languages.get(language, {}).get(string, string)
84 @contextfunction
85 def ngettext(context, s, p, n):
86 language = context.get('LANGUAGE', 'en')
87 if n != 1:
88 return languages.get(language, {}).get(p, p)
89 return languages.get(language, {}).get(s, s)
92 i18n_env = Environment(
93 loader=DictLoader(i18n_templates),
94 extensions=['jinja2.ext.i18n']
96 i18n_env.globals.update({
97 '_': gettext,
98 'gettext': gettext,
99 'ngettext': ngettext
102 newstyle_i18n_env = Environment(
103 loader=DictLoader(newstyle_i18n_templates),
104 extensions=['jinja2.ext.i18n']
106 newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
108 class TestExtension(Extension):
109 tags = set(['test'])
110 ext_attr = 42
112 def parse(self, parser):
113 return nodes.Output([self.call_method('_dump', [
114 nodes.EnvironmentAttribute('sandboxed'),
115 self.attr('ext_attr'),
116 nodes.ImportedName(__name__ + '.importable_object'),
117 nodes.ContextReference()
118 ])]).set_lineno(next(parser.stream).lineno)
120 def _dump(self, sandboxed, ext_attr, imported_object, context):
121 return '%s|%s|%s|%s' % (
122 sandboxed,
123 ext_attr,
124 imported_object,
125 context.blocks
129 class PreprocessorExtension(Extension):
131 def preprocess(self, source, name, filename=None):
132 return source.replace('[[TEST]]', '({{ foo }})')
135 class StreamFilterExtension(Extension):
137 def filter_stream(self, stream):
138 for token in stream:
139 if token.type == 'data':
140 for t in self.interpolate(token):
141 yield t
142 else:
143 yield token
145 def interpolate(self, token):
146 pos = 0
147 end = len(token.value)
148 lineno = token.lineno
149 while 1:
150 match = _gettext_re.search(token.value, pos)
151 if match is None:
152 break
153 value = token.value[pos:match.start()]
154 if value:
155 yield Token(lineno, 'data', value)
156 lineno += count_newlines(token.value)
157 yield Token(lineno, 'variable_begin', None)
158 yield Token(lineno, 'name', 'gettext')
159 yield Token(lineno, 'lparen', None)
160 yield Token(lineno, 'string', match.group(1))
161 yield Token(lineno, 'rparen', None)
162 yield Token(lineno, 'variable_end', None)
163 pos = match.end()
164 if pos < end:
165 yield Token(lineno, 'data', token.value[pos:])
168 class ExtensionsTestCase(JinjaTestCase):
170 def test_extend_late(self):
171 env = Environment()
172 env.add_extension('jinja2.ext.autoescape')
173 t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
174 assert t.render() == '&lt;test&gt;'
176 def test_loop_controls(self):
177 env = Environment(extensions=['jinja2.ext.loopcontrols'])
179 tmpl = env.from_string('''
180 {%- for item in [1, 2, 3, 4] %}
181 {%- if item % 2 == 0 %}{% continue %}{% endif -%}
182 {{ item }}
183 {%- endfor %}''')
184 assert tmpl.render() == '13'
186 tmpl = env.from_string('''
187 {%- for item in [1, 2, 3, 4] %}
188 {%- if item > 2 %}{% break %}{% endif -%}
189 {{ item }}
190 {%- endfor %}''')
191 assert tmpl.render() == '12'
193 def test_do(self):
194 env = Environment(extensions=['jinja2.ext.do'])
195 tmpl = env.from_string('''
196 {%- set items = [] %}
197 {%- for char in "foo" %}
198 {%- do items.append(loop.index0 ~ char) %}
199 {%- endfor %}{{ items|join(', ') }}''')
200 assert tmpl.render() == '0f, 1o, 2o'
202 def test_with(self):
203 env = Environment(extensions=['jinja2.ext.with_'])
204 tmpl = env.from_string('''\
205 {% with a=42, b=23 -%}
206 {{ a }} = {{ b }}
207 {% endwith -%}
208 {{ a }} = {{ b }}\
209 ''')
210 assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
211 == ['42 = 23', '1 = 2']
213 def test_extension_nodes(self):
214 env = Environment(extensions=[TestExtension])
215 tmpl = env.from_string('{% test %}')
216 assert tmpl.render() == 'False|42|23|{}'
218 def test_identifier(self):
219 assert TestExtension.identifier == __name__ + '.TestExtension'
221 def test_rebinding(self):
222 original = Environment(extensions=[TestExtension])
223 overlay = original.overlay()
224 for env in original, overlay:
225 for ext in env.extensions.itervalues():
226 assert ext.environment is env
228 def test_preprocessor_extension(self):
229 env = Environment(extensions=[PreprocessorExtension])
230 tmpl = env.from_string('{[[TEST]]}')
231 assert tmpl.render(foo=42) == '{(42)}'
233 def test_streamfilter_extension(self):
234 env = Environment(extensions=[StreamFilterExtension])
235 env.globals['gettext'] = lambda x: x.upper()
236 tmpl = env.from_string('Foo _(bar) Baz')
237 out = tmpl.render()
238 assert out == 'Foo BAR Baz'
240 def test_extension_ordering(self):
241 class T1(Extension):
242 priority = 1
243 class T2(Extension):
244 priority = 2
245 env = Environment(extensions=[T1, T2])
246 ext = list(env.iter_extensions())
247 assert ext[0].__class__ is T1
248 assert ext[1].__class__ is T2
251 class InternationalizationTestCase(JinjaTestCase):
253 def test_trans(self):
254 tmpl = i18n_env.get_template('child.html')
255 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
257 def test_trans_plural(self):
258 tmpl = i18n_env.get_template('plural.html')
259 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
260 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
262 def test_complex_plural(self):
263 tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
264 'pluralize count %}{{ count }} items{% endtrans %}')
265 assert tmpl.render() == '2 items'
266 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
267 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
269 def test_trans_stringformatting(self):
270 tmpl = i18n_env.get_template('stringformat.html')
271 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
273 def test_extract(self):
274 from jinja2.ext import babel_extract
275 source = BytesIO('''
276 {{ gettext('Hello World') }}
277 {% trans %}Hello World{% endtrans %}
278 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
279 '''.encode('ascii')) # make python 3 happy
280 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
281 (2, 'gettext', u'Hello World', []),
282 (3, 'gettext', u'Hello World', []),
283 (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
286 def test_comment_extract(self):
287 from jinja2.ext import babel_extract
288 source = BytesIO('''
289 {# trans first #}
290 {{ gettext('Hello World') }}
291 {% trans %}Hello World{% endtrans %}{# trans second #}
292 {#: third #}
293 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
294 '''.encode('utf-8')) # make python 3 happy
295 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
296 (3, 'gettext', u'Hello World', ['first']),
297 (4, 'gettext', u'Hello World', ['second']),
298 (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
302 class NewstyleInternationalizationTestCase(JinjaTestCase):
304 def test_trans(self):
305 tmpl = newstyle_i18n_env.get_template('child.html')
306 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
308 def test_trans_plural(self):
309 tmpl = newstyle_i18n_env.get_template('plural.html')
310 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
311 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
313 def test_complex_plural(self):
314 tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
315 'pluralize count %}{{ count }} items{% endtrans %}')
316 assert tmpl.render() == '2 items'
317 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
318 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
320 def test_trans_stringformatting(self):
321 tmpl = newstyle_i18n_env.get_template('stringformat.html')
322 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
324 def test_newstyle_plural(self):
325 tmpl = newstyle_i18n_env.get_template('ngettext.html')
326 assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
327 assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
329 def test_autoescape_support(self):
330 env = Environment(extensions=['jinja2.ext.autoescape',
331 'jinja2.ext.i18n'])
332 env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
333 lambda s, p, n: s, newstyle=True)
334 t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
335 '"<test>") }}{% endautoescape %}')
336 assert t.render(ae=True) == '<strong>Wert: &lt;test&gt;</strong>'
337 assert t.render(ae=False) == '<strong>Wert: <test></strong>'
339 def test_num_used_twice(self):
340 tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
341 assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
343 def test_num_called_num(self):
344 source = newstyle_i18n_env.compile('''
345 {% trans num=3 %}{{ num }} apple{% pluralize
346 %}{{ num }} apples{% endtrans %}
347 ''', raw=True)
348 # quite hacky, but the only way to properly test that. The idea is
349 # that the generated code does not pass num twice (although that
350 # would work) for better performance. This only works on the
351 # newstyle gettext of course
352 assert re.search(r"l_ngettext, u?'\%\(num\)s apple', u?'\%\(num\)s "
353 r"apples', 3", source) is not None
355 def test_trans_vars(self):
356 t1 = newstyle_i18n_env.get_template('transvars1.html')
357 t2 = newstyle_i18n_env.get_template('transvars2.html')
358 t3 = newstyle_i18n_env.get_template('transvars3.html')
359 assert t1.render(num=1, LANGUAGE='de') == 'Benutzer: 1'
360 assert t2.render(count=23, LANGUAGE='de') == 'Benutzer: 23'
361 assert t3.render(num=42, LANGUAGE='de') == 'Benutzer: 42'
363 def test_novars_vars_escaping(self):
364 t = newstyle_i18n_env.get_template('novars.html')
365 assert t.render() == '%(hello)s'
366 t = newstyle_i18n_env.get_template('vars.html')
367 assert t.render(foo='42') == '42%(foo)s'
368 t = newstyle_i18n_env.get_template('explicitvars.html')
369 assert t.render() == '%(foo)s'
372 class AutoEscapeTestCase(JinjaTestCase):
374 def test_scoped_setting(self):
375 env = Environment(extensions=['jinja2.ext.autoescape'],
376 autoescape=True)
377 tmpl = env.from_string('''
378 {{ "<HelloWorld>" }}
379 {% autoescape false %}
380 {{ "<HelloWorld>" }}
381 {% endautoescape %}
382 {{ "<HelloWorld>" }}
383 ''')
384 assert tmpl.render().split() == \
385 [u'&lt;HelloWorld&gt;', u'<HelloWorld>', u'&lt;HelloWorld&gt;']
387 env = Environment(extensions=['jinja2.ext.autoescape'],
388 autoescape=False)
389 tmpl = env.from_string('''
390 {{ "<HelloWorld>" }}
391 {% autoescape true %}
392 {{ "<HelloWorld>" }}
393 {% endautoescape %}
394 {{ "<HelloWorld>" }}
395 ''')
396 assert tmpl.render().split() == \
397 [u'<HelloWorld>', u'&lt;HelloWorld&gt;', u'<HelloWorld>']
399 def test_nonvolatile(self):
400 env = Environment(extensions=['jinja2.ext.autoescape'],
401 autoescape=True)
402 tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
403 assert tmpl.render() == ' foo="&lt;test&gt;"'
404 tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
405 '|xmlattr|escape }}{% endautoescape %}')
406 assert tmpl.render() == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
408 def test_volatile(self):
409 env = Environment(extensions=['jinja2.ext.autoescape'],
410 autoescape=True)
411 tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
412 '|xmlattr|escape }}{% endautoescape %}')
413 assert tmpl.render(foo=False) == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
414 assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
416 def test_scoping(self):
417 env = Environment(extensions=['jinja2.ext.autoescape'])
418 tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
419 '{% endautoescape %}{{ x }}{{ "<y>" }}')
420 assert tmpl.render(x=1) == '&lt;x&gt;1<y>'
422 def test_volatile_scoping(self):
423 env = Environment(extensions=['jinja2.ext.autoescape'])
424 tmplsource = '''
425 {% autoescape val %}
426 {% macro foo(x) %}
427 [{{ x }}]
428 {% endmacro %}
429 {{ foo().__class__.__name__ }}
430 {% endautoescape %}
431 {{ '<testing>' }}
433 tmpl = env.from_string(tmplsource)
434 assert tmpl.render(val=True).split()[0] == 'Markup'
435 assert tmpl.render(val=False).split()[0] == unicode.__name__
437 # looking at the source we should see <testing> there in raw
438 # (and then escaped as well)
439 env = Environment(extensions=['jinja2.ext.autoescape'])
440 pysource = env.compile(tmplsource, raw=True)
441 assert '<testing>\\n' in pysource
443 env = Environment(extensions=['jinja2.ext.autoescape'],
444 autoescape=True)
445 pysource = env.compile(tmplsource, raw=True)
446 assert '&lt;testing&gt;\\n' in pysource
449 def suite():
450 suite = unittest.TestSuite()
451 suite.addTest(unittest.makeSuite(ExtensionsTestCase))
452 suite.addTest(unittest.makeSuite(InternationalizationTestCase))
453 suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
454 suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
455 return suite