# -*- coding: utf-8 -*- import unittest from bottle import SimpleTemplate, TemplateError, view, template, touni, tob, html_quote import re import traceback class TestSimpleTemplate(unittest.TestCase): def assertRenders(self, tpl, to, *args, **vars): if isinstance(tpl, str): tpl = SimpleTemplate(tpl) self.assertEqual(touni(to), tpl.render(*args, **vars)) def test_string(self): """ Templates: Parse string""" self.assertRenders('start {{var}} end', 'start var end', var='var') def test_self_as_variable_name(self): self.assertRenders('start {{self}} end', 'start var end', {'self':'var'}) def test_file(self): t = SimpleTemplate(name='./views/stpl_simple.tpl') self.assertRenders(t, 'start var end\n', var='var') def test_name(self): t = SimpleTemplate(name='stpl_simple', lookup=['./views/']) self.assertRenders(t, 'start var end\n', var='var') def test_unicode(self): self.assertRenders('start {{var}} end', 'start äöü end', var=touni('äöü')) self.assertRenders('start {{var}} end', 'start äöü end', var=tob('äöü')) def test_unicode_code(self): """ Templates: utf8 code in file""" t = SimpleTemplate(name='./views/stpl_unicode.tpl') self.assertRenders(t, 'start ñç äöü end\n', var=touni('äöü')) def test_import(self): """ Templates: import statement""" t = '%from base64 import b64encode\nstart {{b64encode(var.encode("ascii") if hasattr(var, "encode") else var)}} end' self.assertRenders(t, 'start dmFy end', var='var') def test_data(self): """ Templates: Data representation """ t = SimpleTemplate('<{{var}}>') self.assertRenders('<{{var}}>', '', var=True) self.assertRenders('<{{var}}>', '', var=False) self.assertRenders('<{{var}}>', '', var=None) self.assertRenders('<{{var}}>', '<0>', var=0) self.assertRenders('<{{var}}>', '<5>', var=5) self.assertRenders('<{{var}}>', '', var=tob('b')) self.assertRenders('<{{var}}>', '<1.0>', var=1.0) self.assertRenders('<{{var}}>', '<[1, 2]>', var=[1,2]) def test_htmlutils_quote(self): self.assertEquals('"<' "\\>"', html_quote('<\'\r\n\t"\\>')); def test_escape(self): self.assertRenders('<{{var}}>', '', var='b') self.assertRenders('<{{var}}>', '<<&>>',var='<&>') def test_noescape(self): self.assertRenders('<{{!var}}>', '', var='b') self.assertRenders('<{{!var}}>', '<<&>>', var='<&>') def test_noescape_setting(self): t = SimpleTemplate('<{{var}}>', noescape=True) self.assertRenders(t, '', var='b') self.assertRenders(t, '<<&>>', var='<&>') t = SimpleTemplate('<{{!var}}>', noescape=True) self.assertRenders(t, '', var='b') self.assertRenders(t, '<<&>>', var='<&>') def test_blocks(self): """ Templates: Code blocks and loops """ t = "start\n%for i in l:\n{{i}} \n%end\nend" self.assertRenders(t, 'start\n1 \n2 \n3 \nend', l=[1,2,3]) self.assertRenders(t, 'start\nend', l=[]) t = "start\n%if i:\n{{i}} \n%end\nend" self.assertRenders(t, 'start\nTrue \nend', i=True) self.assertRenders(t, 'start\nend', i=False) def test_elsebug(self): ''' Whirespace between block keyword and colon is allowed ''' self.assertRenders("%if 1:\nyes\n%else:\nno\n%end\n", "yes\n") self.assertRenders("%if 1:\nyes\n%else :\nno\n%end\n", "yes\n") def test_commentbug(self): ''' A "#" sign within an string is not a comment ''' self.assertRenders("%if '#':\nyes\n%end\n", "yes\n") def test_multiline(self): ''' Block statements with non-terminating newlines ''' self.assertRenders("%if 1\\\n%and 1:\nyes\n%end\n", "yes\n") def test_newline_in_parameterlist(self): ''' Block statements with non-terminating newlines in list ''' self.assertRenders("%a=[1,\n%2]\n{{len(a)}}", "2") def test_dedentbug(self): ''' One-Line dednet blocks should not change indention ''' t = '%if x: a="if"\n%else: a="else"\n%end\n{{a}}' self.assertRenders(t, "if", x=True) self.assertRenders(t, "else", x=False) t = '%if x:\n%a="if"\n%else: a="else"\n%end\n{{a}}' self.assertRenders(t, "if", x=True) self.assertRenders(t, "else", x=False) t = SimpleTemplate('%if x: a="if"\n%else: a="else"\n%end') self.assertRaises(NameError, t.render) def test_onelinebugs(self): ''' One-Line blocks should not change indention ''' t = '%if x:\n%a=1\n%end\n{{a}}' self.assertRenders(t, "1", x=True) t = '%if x: a=1; end\n{{a}}' self.assertRenders(t, "1", x=True) t = '%if x:\n%a=1\n%else:\n%a=2\n%end\n{{a}}' self.assertRenders(t, "1", x=True) self.assertRenders(t, "2", x=False) t = '%if x: a=1\n%else:\n%a=2\n%end\n{{a}}' self.assertRenders(t, "1", x=True) self.assertRenders(t, "2", x=False) t = '%if x:\n%a=1\n%else: a=2; end\n{{a}}' self.assertRenders(t, "1", x=True) self.assertRenders(t, "2", x=False) t = '%if x: a=1\n%else: a=2; end\n{{a}}' self.assertRenders(t, "1", x=True) self.assertRenders(t, "2", x=False) def test_onelineblocks(self): """ Templates: one line code blocks """ t = "start\n%a=''\n%for i in l: a += str(i); end\n{{a}}\nend" self.assertRenders(t, 'start\n123\nend', l=[1,2,3]) self.assertRenders(t, 'start\n\nend', l=[]) def test_escaped_codelines(self): self.assertRenders('%% test', '% test') self.assertRenders('%%% test', '%% test') self.assertRenders('\\% test', '% test') self.assertRenders('\\%% test', '%% test') def test_nobreak(self): """ Templates: Nobreak statements""" self.assertRenders("start\\\\\n%pass\nend", 'startend') def test_nonobreak(self): """ Templates: Escaped nobreak statements""" self.assertRenders("start\\\\\n\\\\\n%pass\nend", 'start\\\\\nend') def test_include(self): """ Templates: Include statements""" t = SimpleTemplate(name='stpl_include', lookup=['./views/']) self.assertRenders(t, 'before\nstart var end\nafter\n', var='var') def test_rebase(self): """ Templates: %rebase and method passing """ t = SimpleTemplate(name='stpl_t2main', lookup=['./views/']) result='+base+\n+main+\n!1234!\n+include+\n-main-\n+include+\n-base-\n' self.assertRenders(t, result, content='1234') def test_get(self): self.assertRenders('{{get("x", "default")}}', '1234', x='1234') self.assertRenders('{{get("x", "default")}}', 'default') def test_setdefault(self): t = '%setdefault("x", "default")\n{{x}}' self.assertRenders(t, '1234', x='1234') self.assertRenders(t, 'default') def test_defnied(self): self.assertRenders('{{x if defined("x") else "no"}}', 'yes', x='yes') self.assertRenders('{{x if defined("x") else "no"}}', 'no') def test_notfound(self): """ Templates: Unavailable templates""" self.assertRaises(TemplateError, SimpleTemplate, name="abcdef") def test_error(self): """ Templates: Exceptions""" self.assertRaises(SyntaxError, lambda: SimpleTemplate('%for badsyntax').co) self.assertRaises(IndexError, SimpleTemplate('{{i[5]}}').render, i=[0]) def test_winbreaks(self): """ Templates: Test windows line breaks """ self.assertRenders('%var+=1\r\n{{var}}\r\n', '6\r\n', var=5) def test_winbreaks_end_bug(self): d = { 'test': [ 1, 2, 3 ] } self.assertRenders('%for i in test:\n{{i}}\n%end\n', '1\n2\n3\n', **d) self.assertRenders('%for i in test:\n{{i}}\r\n%end\n', '1\r\n2\r\n3\r\n', **d) self.assertRenders('%for i in test:\r\n{{i}}\n%end\r\n', '1\n2\n3\n', **d) self.assertRenders('%for i in test:\r\n{{i}}\r\n%end\r\n', '1\r\n2\r\n3\r\n', **d) def test_commentonly(self): """ Templates: Commentd should behave like code-lines (e.g. flush text-lines) """ t = SimpleTemplate('...\n%#test\n...') self.assertNotEqual('#test', t.code.splitlines()[0]) def test_detect_pep263(self): ''' PEP263 strings in code-lines change the template encoding on the fly ''' t = SimpleTemplate(touni('%#coding: iso8859_15\nöäü?@€').encode('utf8')) self.assertNotEqual(touni('öäü?@€'), t.render()) self.assertEqual(t.encoding, 'iso8859_15') t = SimpleTemplate(touni('%#coding: iso8859_15\nöäü?@€').encode('iso8859_15')) self.assertEqual(touni('öäü?@€'), t.render()) self.assertEqual(t.encoding, 'iso8859_15') self.assertEqual(2, len(t.code.splitlines())) def test_ignore_pep263_in_textline(self): ''' PEP263 strings in text-lines have no effect ''' t = SimpleTemplate(touni('#coding: iso8859_15\nöäü?@€').encode('utf8')) self.assertEqual(touni('#coding: iso8859_15\nöäü?@€'), t.render()) self.assertEqual(t.encoding, 'utf8') def test_ignore_late_pep263(self): ''' PEP263 strings must appear within the first two lines ''' t = SimpleTemplate(touni('\n\n%#coding: iso8859_15\nöäü?@€').encode('utf8')) self.assertEqual(touni('\n\nöäü?@€'), t.render()) self.assertEqual(t.encoding, 'utf8') def test_coding_stress(self): self.assertRenders('%a=1\n%coding=a\nok', 'ok') self.assertRenders('a %coding:b', 'a %coding:b') self.assertRenders(' % #coding:utf-8', '') def test_template_shortcut(self): result = template('start {{var}} end', var='middle') self.assertEqual(touni('start middle end'), result) def test_view_decorator(self): @view('start {{var}} end') def test(): return dict(var='middle') self.assertEqual(touni('start middle end'), test()) def test_view_decorator_issue_407(self): @view('stpl_no_vars') def test(): pass self.assertEqual(touni('hihi'), test()) @view('aaa {{x}}', x='bbb') def test2(): pass self.assertEqual(touni('aaa bbb'), test2()) def test_global_config(self): SimpleTemplate.global_config('meh', 1) t = SimpleTemplate('anything') self.assertEqual(touni('anything'), t.render()) def test_bug_no_whitespace_before_stmt(self): self.assertRenders('\n{{var}}', '\nx', var='x') def test_bug_block_keywords_eat_prefixed_code(self): ''' #595: Everything before an 'if' statement is removed, resulting in SyntaxError. ''' tpl = "% m = 'x' if True else 'y'\n{{m}}" self.assertRenders(tpl, 'x') class TestSTPLDir(unittest.TestCase): def fix_ident(self, string): lines = string.splitlines(True) if not lines: return string if not lines[0].strip(): lines.pop(0) whitespace = re.match('([ \t]*)', lines[0]).group(0) if not whitespace: return string for i in range(len(lines)): lines[i] = lines[i][len(whitespace):] return lines[0][:0].join(lines) def assertRenders(self, source, result, syntax=None, *args, **vars): source = self.fix_ident(source) result = self.fix_ident(result) tpl = SimpleTemplate(source, syntax=syntax) try: tpl.co self.assertEqual(touni(result), tpl.render(*args, **vars)) except SyntaxError: self.fail('Syntax error in template:\n%s\n\nTemplate code:\n##########\n%s\n##########' % (traceback.format_exc(), tpl.code)) def test_old_include(self): t1 = SimpleTemplate('%include foo') t1.cache['foo'] = SimpleTemplate('foo') self.assertEqual(t1.render(), 'foo') def test_old_include_with_args(self): t1 = SimpleTemplate('%include foo x=y') t1.cache['foo'] = SimpleTemplate('foo{{x}}') self.assertEqual(t1.render(y='bar'), 'foobar') def test_defect_coding(self): t1 = SimpleTemplate('%#coding comment\nfoo{{y}}') self.assertEqual(t1.render(y='bar'), 'foobar') def test_multiline_block(self): source = ''' <% a = 5 b = 6 c = 7 %> {{a+b+c}} '''; result = ''' 18 ''' self.assertRenders(source, result) source_wineol = '<% a = 5\r\nb = 6\r\nc = 7\r\n%>\r\n{{a+b+c}}' result_wineol = '18' self.assertRenders(source_wineol, result_wineol) def test_multiline_ignore_eob_in_string(self): source = ''' <% x=5 # a comment y = '%>' # a string # this is still code # lets end this %> {{x}}{{!y}} '''; result = ''' 5%> ''' self.assertRenders(source, result) def test_multiline_find_eob_in_comments(self): source = ''' <% # a comment # %> ignore because not end of line # this is still code x=5 # lets end this here %> {{x}} '''; result = ''' 5 ''' self.assertRenders(source, result) def test_multiline_indention(self): source = ''' <% if True: a = 2 else: a = 0 end %> {{a}} '''; result = ''' 2 ''' self.assertRenders(source, result) def test_multiline_eob_after_end(self): source = ''' <% if True: a = 2 end %> {{a}} '''; result = ''' 2 ''' self.assertRenders(source, result) def test_multiline_eob_in_single_line_code(self): # eob must be a valid python expression to allow this test. source = ''' cline eob=5; eob xxx '''; result = ''' xxx ''' self.assertRenders(source, result, syntax='sob eob cline foo bar') def test_multiline_strings_in_code_line(self): source = ''' % a = """line 1 line 2""" {{a}} '''; result = ''' line 1 line 2 ''' self.assertRenders(source, result) def test_multiline_comprehensions_in_code_line(self): self.assertRenders(source=''' % a = [ % (i + 1) % for i in range(5) % if i%2 == 0 % ] {{a}} ''', result=''' [1, 3, 5] ''') if __name__ == '__main__': #pragma: no cover unittest.main()