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'):
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):}}<div>{{=k}}</div>{{pass}}')
@template(body='[[for k in range(a):]]<div>[[=k]]</div>[[pass]]', delimiters="[[ ]]")
def test():
return dict(a=3)
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='"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('<script></script>')}}""")
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}),
'<script></script>')
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),
'<script></script>')