fixed template reader
This commit is contained in:
@@ -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>'
|
||||||
|
|||||||
@@ -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}}"
|
||||||
@@ -109,27 +109,27 @@ class TestTemplate(unittest.TestCase):
|
|||||||
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>')
|
||||||
|
|||||||
Reference in New Issue
Block a user