From 174c1fb31d12d12d8c75b07412bafa028e98f2fc Mon Sep 17 00:00:00 2001 From: mdipierro Date: Tue, 1 Jan 2019 20:34:17 -0800 Subject: [PATCH] fixed template reader --- gluon/packages/template/template.py | 94 ++++++++++++++++------------- gluon/tests/test_template.py | 58 +++++++++--------- 2 files changed, 80 insertions(+), 72 deletions(-) diff --git a/gluon/packages/template/template.py b/gluon/packages/template/template.py index c10310ca..1ddcdf27 100644 --- a/gluon/packages/template/template.py +++ b/gluon/packages/template/template.py @@ -37,18 +37,14 @@ else: def to_native(obj, charset='utf8', errors='strict'): return ibj if isinstance(obj, str) else obj.decode(charset, errors) -CACHE = {} -def cached_read(filename, mode='rb'): - if filename in CACHE: - return CACHE[filename] - try: - with open(filename, mode) as fp: - body = fp.read() - CACHE[filename] = body - return body - except IOError: - raise RestrictedError(filename, '', 'Unable to find the file') +DEFAULT_DELIMITERS = ('{{', '}}') + + +def file_reader(filename, mode='rb'): + with open(filename, mode) as fp: + body = fp.read() + return body try: # have web2py @@ -118,14 +114,14 @@ class BlockNode(Node): {{ end }} """ - def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')): + def __init__(self, name='', pre_extend=False, delimiters=None): """ name - Name of this Node. """ self.nodes = [] self.name = name self.pre_extend = pre_extend - self.left, self.right = delimiters + self.left, self.right = delimiters or DEFAULT_DELIMITERS def __repr__(self): lines = ['%sblock %s%s' % (self.left, self.name, self.right)] @@ -260,7 +256,7 @@ class TemplateParser(object): """ - default_delimiters = ('{{', '}}') + default_delimiters = DEFAULT_DELIMITERS r_tag = compile(r'(\{\{.*?\}\})', DOTALL) r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL) @@ -279,15 +275,18 @@ class TemplateParser(object): context=dict(), path='views/', writer='response.write', - lexers={}, - delimiters=('{{', '}}'), - _super_nodes = [], + lexers=None, + delimiters=None, + _super_nodes = None, + reader=None, ): # Keep a root level name. self.name = name # Raw text to start parsing. self.text = text + # use the default reader + self.reader = reader or file_reader # Writer to use (refer to the default for an example). # This will end up as # "%s(%s, escape=False)" % (self.writer, value) @@ -297,6 +296,10 @@ class TemplateParser(object): self.lexers = lexers else: self.lexers = {} + if _super_nodes is None: + _super_nodes = [] + if delimiters is None: + delimiters = DEFAULT_DELIMITERS # Path of templates self.path = path @@ -471,9 +474,7 @@ class TemplateParser(object): # try to read the text. try: - fileobj = open(filepath, 'rb') - text = fileobj.read() - fileobj.close() + text = self.reader(filepath) except IOError: self._raise_error('Unable to open included view file: ' + filepath) text = to_native(text) @@ -490,7 +491,8 @@ class TemplateParser(object): context=self.context, path=self.path, writer=self.writer, - delimiters=self.delimiters) + delimiters=self.delimiters, + reader=self.reader) content.append(t.content) @@ -793,9 +795,9 @@ class TemplateParser(object): def parse_template(filename, path='views/', - context=dict(), - lexers={}, - delimiters=('{{', '}}') + context=None, + lexers=None, + delimiters=None ): """ Args: @@ -805,11 +807,14 @@ def parse_template(filename, lexers: dict of custom lexers to use delimiters: opening and closing tags """ - + context = context or {} + lexers = lexers or {} + delimiters = delimiters or DEFAULT_DELIMITERS + reader = reader or file_reader # First, if we have a str try to open the file if isinstance(filename, basestring): fname = os.path.join(path, filename) - text = cached_read(fname) + text = file_reader(fname) else: text = filename.read() text = to_native(text) @@ -865,7 +870,8 @@ def render(content="hello world", context=None, lexers=None, delimiters='{{ }}', - writer='response.write' + writer='response.write', + reader=None ): """ Generic render function @@ -914,6 +920,8 @@ def render(content="hello world", lexers = {} if isinstance(delimiters, basestring): delimiters = delimiters.split(' ',1) + if not reader: + reader = file_reader # here to avoid circular Imports try: @@ -941,19 +949,22 @@ def render(content="hello world", if not content and not stream and not filename: raise SyntaxError("Must specify a stream or filename or content") - # Here for legacy purposes, probably can be reduced to - # something more simple. - close_stream = False - if not stream: - if filename: - stream = open(filename, 'rb') - close_stream = True - elif content: - stream = StringIO(to_native(content)) + if not content: + if stream: + content = stream.read() + elif filename: + content = reader(filename) + else: + content = '(no template found)' # Execute the template. - code = str(TemplateParser(stream.read( - ), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer)) + code = str(TemplateParser(content, + context=context, + path=path, + lexers=lexers, + delimiters=delimiters, + writer=writer, + reader=reader)) try: exec(code, context) @@ -961,9 +972,6 @@ def render(content="hello world", # for i,line in enumerate(code.split('\n')): print i,line raise - if close_stream: - stream.close() - # Returned the rendered content. text = context['response'].body.getvalue() if old_response_body is not None: @@ -988,7 +996,7 @@ class template(object): if self.body: body = self.body else: - body = cached_read(filename) + body = file_reader(filename) return render( content=body, path=self.path, @@ -1000,7 +1008,7 @@ class template(object): return wrapper if __name__ == '__main__': - @template(body='{{for k in range(a):}}
{{=k}}
{{pass}}') + @template(body='[[for k in range(a):]]
[[=k]]
[[pass]]', delimiters="[[ ]]") def test(): return dict(a=3) assert test() == '
0
1
2
' diff --git a/gluon/tests/test_template.py b/gluon/tests/test_template.py index 9d21d775..e64df7f8 100644 --- a/gluon/tests/test_template.py +++ b/gluon/tests/test_template.py @@ -24,11 +24,11 @@ class TestTemplate(unittest.TestCase): self.assertEqual(render(content='"abc"'), '"abc"') self.assertEqual(render(content='"a\'bc"'), '"a\'bc"') self.assertEqual(render(content='"a\"bc"'), '"a\"bc"') - self.assertEqual(render(content=r'''"a\"bc"'''), r'"a\"bc"') - self.assertEqual(render(content=r'''"""abc\""""'''), r'"""abc\""""') + self.assertEqual(render(content=r'"a\"bc"'), r'"a\"bc"') + self.assertEqual(render(content=r'"""abc\""""'), r'"""abc\""""') def testEqualWrite(self): - "test generation of response.write from =" + "test generation of response.write" self.assertEqual(render(content='{{=2+2}}'), '4') self.assertEqual(render(content='{{="abc"}}'), 'abc') # whitespace is stripped @@ -81,7 +81,7 @@ class TestTemplate(unittest.TestCase): else: setattr(module, fn_name, unpatch) - def dummy_open(path, mode): + def dummy_open(path): if path == pjoin('views', 'layout.html'): return StringIO("{{block left_sidebar}}left{{end}}" "{{include}}" @@ -108,28 +108,28 @@ class TestTemplate(unittest.TestCase): elif path == pjoin('views', 'default', 'noescape.html'): return StringIO("""{{=NOESCAPE('')}}""") raise IOError - - with monkey_patch(template, 'open', dummy_open): - self.assertEqual( - render(filename=pjoin('views', 'default', 'index.html'), - path='views'), - 'left to right') - self.assertEqual( - render(filename=pjoin('views', 'default', 'indexbrackets.html'), - path='views', delimiters=('[[', ']]')), - 'left to right') - self.assertRaises( - RestrictedError, - render, - filename=pjoin('views', 'default', 'missing.html'), - path='views') - response = template.DummyResponse() - response.delimiters = ('[[', ']]') - self.assertEqual( - render(filename=pjoin('views', 'default', 'indexbrackets.html'), - path='views', context={'response': response}), - 'left to right') - self.assertEqual( - render(filename=pjoin('views', 'default', 'noescape.html'), - context={'NOESCAPE': template.NOESCAPE}), - '') + + self.assertEqual( + render(filename=pjoin('views', 'default', 'index.html'), + path='views', reader=dummy_open), + 'left to right') + self.assertEqual( + render(filename=pjoin('views', 'default', 'indexbrackets.html'), + path='views', delimiters=('[[', ']]'), reader=dummy_open), + 'left to right') + self.assertRaises( + RestrictedError, + render, + filename=pjoin('views', 'default', 'missing.html'), + path='views', + reader=dummy_open) + response = template.DummyResponse() + response.delimiters = ('[[', ']]') + self.assertEqual( + render(filename=pjoin('views', 'default', 'indexbrackets.html'), + path='views', context={'response': response}, reader=dummy_open), + 'left to right') + self.assertEqual( + render(filename=pjoin('views', 'default', 'noescape.html'), + context={'NOESCAPE': template.NOESCAPE}, reader=dummy_open), + '')