fixed template reader

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