##
## parse template with recognizing '#endfor', '#endif', and so on.
##
## ex:
## import tenjin
## from tenjin.helpers import *
## from my_template import MyTemplate
## engine = tenjin.Engine(templateclass=MyTemplate)
## print("------------- script")
## print(engine.get_template("file.pyhtml").script)
## print("------------- output")
## print(engine.render("file.pyhtml")
##
import re
import tenjin
class TemplateSyntaxError(Exception):
pass
def _args2dict(*args):
return dict([ (w, w) for w in args ])
START_WORDS = _args2dict('for', 'if', 'while', 'def', 'try:', 'with', 'class')
END_WORDS = _args2dict('#endfor', '#endif', '#endwhile', '#enddef', '#endtry', '#endwith', '#endclass')
CONT_WORDS = _args2dict('elif', 'else:', 'except', 'except:', 'finally:')
class MyTemplate(tenjin.Template):
def parse_stmts(self, buf, input):
if not input:
return
rexp = self.stmt_pattern()
is_bol = True
index = 0
for m in rexp.finditer(input):
mspace, code, rspace = m.groups()
#mspace, close, rspace = m.groups()
#code = input[m.start()+4+len(mspace):m.end()-len(close)-(rspace and len(rspace) or 0)]
text = input[index:m.start()]
index = m.end()
## detect spaces at beginning of line
lspace = None
if text == '':
if is_bol:
lspace = ''
elif text[-1] == '\n':
lspace = ''
else:
rindex = text.rfind('\n')
if rindex < 0:
if is_bol and text.isspace():
lspace = text
text = ''
else:
s = text[rindex+1:]
if s.isspace():
lspace = s
text = text[:rindex+1]
#is_bol = rspace is not None
## add text, spaces, and statement
self.parse_exprs(buf, text, is_bol)
is_bol = rspace is not None
#if lspace:
# buf.append(lspace)
#if mspace != " ":
# #buf.append(mspace)
# buf.append(mspace == "\t" and "\t" or "\n") # don't append "\r\n"!
if code:
code = self.statement_hook(code)
self.add_stmt(buf, code)
#self._set_spaces(code, lspace, mspace)
#if rspace:
# #buf.append(rspace)
# buf.append("\n") # don't append "\r\n"!
rest = input[index:]
if rest:
self.parse_exprs(buf, rest)
def parse_exprs(self, buf, input, is_bol=False):
buf2 = []
tenjin.Template.parse_exprs(self, buf2, input, is_bol)
if buf2:
buf.append(''.join(buf2))
def add_stmt(self, buf, code):
if not code: return
lines = code.splitlines()
if lines[-1][-1] != "\n":
lines[-1] = lines[-1] + "\n"
buf.extend(lines)
def after_convert(self, buf):
tenjin.Template.after_convert(self, buf)
block = self.parse_lines(buf)
buf[:] = []
self._join_block(block, buf, 0)
depth = -1
##
## ex.
## input = r"""
## if items:
## _buf.extend(('<ul>\n', ))
## i = 0
## for item in items:
## i += 1
## _buf.extend(('<li>', to_str(item), '</li>\n', ))
## #endfor
## _buf.extend(('</ul>\n', ))
## #endif
## """[1:]
## lines = input.splitlines()
## block = self.parse_lines(lines)
## #=> [ "if items:\n",
## [ "_buf.extend(('<ul>\n', ))\n",
## "i = 0\n",
## "for item in items:\n",
## [ "i += 1\n",
## "_buf.extend(('<li>', to_str(item), '</li>\n', ))\n",
## ],
## "#endfor\n",
## "_buf.extend(('</ul>\n', ))\n",
## ],
## "#endif\n",
## ]
def parse_lines(self, lines):
block = []
try:
self._parse_lines(lines.__iter__(), False, block)
except StopIteration:
if self.depth > 0:
raise TemplateSyntaxError("unexpected EOF.")
else:
#raise TemplateSyntaxError("unexpected syntax.")
pass
return block
def _parse_lines(self, iter, end_block, block=None):
if block is None: block = []
START_WORDS_ = START_WORDS
END_WORDS_ = END_WORDS
CONT_WORDS_ = CONT_WORDS
while True:
line = iter.next()
m = re.search(r'\S+', line)
if not m:
block.append(line)
continue
word = m.group(0)
if word in END_WORDS_:
if word != end_block:
raise TemplateSyntaxError("'%s' exptexted buf got '%s'." % (end_block, word))
return block, line, False
elif line.endswith(':\n'):
if word in CONT_WORDS_:
return block, line, True
elif word in START_WORDS_:
block.append(line)
self.depth += 1
child_block, line, has_sibling = self._parse_lines(iter, '#end'+word)
block.extend((child_block, line, ))
while has_sibling:
child_block, line, has_sibling = self._parse_lines(iter, '#end'+word)
block.extend((child_block, line, ))
self.depth -= 1
else:
block.append(line)
else:
block.append(line)
assert "unreachable"
#def join_block(self, block):
# buf = []
# depth = 0
# self._join_block(block, buf, depth)
# return ''.join(buf)
def _join_block(self, block, buf, depth):
indent = ' ' * depth
for line in block:
if isinstance(line, list):
self._join_block(line, buf, depth+1)
else:
buf.append(indent + line.lstrip())
if __name__ == '__main__':
import sys
if len(sys.argv) > 1:
filename = sys.argv[1]
template = MyTemplate(filename)
print(template.script)
else: # test
input = r"""
<html>
<body>
<?py if items: ?>
<table>
<?py i = 0 ?>
<?py for item in items: ?>
<?py i += 1 ?>
<?py klass = i % 2 and 'odd' or 'even' ?>
<tr class="#{klass}">
<td>#{i}</li>
<td>${item}</li>
</tr>
<?py else: ?>
<p>nothing.</p>
<?py #endfor ?>
</table>
<?py else: ?>
<p>Not found.</p>
<?py #endif ?>
</body>
</html>
"""[1:]
expected = r"""
_buf.extend(('''<html>
<body>\n''', ));
if items:
_buf.extend((''' <table>\n''', ));
i = 0
for item in items:
i += 1
klass = i % 2 and 'odd' or 'even'
_buf.extend((''' <tr class="''', to_str(klass), '''">
<td>''', to_str(i), '''</li>
<td>''', escape(to_str(item)), '''</li>
</tr>\n''', ));
else:
_buf.extend((''' <p>nothing.</p>\n''', ));
#endfor
_buf.extend((''' </table>\n''', ));
else:
_buf.extend((''' <p>Not found.</p>\n''', ));
#endif
_buf.extend((''' </body>
</html>\n''', ));
"""[1:]
#
template = MyTemplate()
actual = template.convert(input)
assert expected == actual
#print(actual)
#import pprint
#pprint.pprint(result)
## if-statement
input = r"""
<?py for x in nums: ?>
<?py if x > 0: ?>
<p>Positive.</p>
<?py elif x < 0: ?>
<p>Negative.</p>
<?py else: ?>
<p>Zero.</p>
<?py #endfor ?>
<?py #endfor ?>
"""[1:]
expected = r"""
for x in nums:
if x > 0:
_buf.extend(('''<p>Positive.</p>\n''', ));
elif x < 0:
_buf.extend(('''<p>Negative.</p>\n''', ));
else:
_buf.extend(('''<p>Zero.</p>\n''', ));
#endif
#endfor
"""[1:]
actual = template.convert(input)
assert expected == actual