Skip to content

Instantly share code, notes, and snippets.

@kwatch
Created June 13, 2009 15:49
Show Gist options
  • Save kwatch/129297 to your computer and use it in GitHub Desktop.
Save kwatch/129297 to your computer and use it in GitHub Desktop.
##
## 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(SyntaxError):
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:')
_WORD_REXP = re.compile(r'\S+')
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 mspace == "\n":
code = "\n" + (code or "")
if rspace == "\n":
code = (code or "") + "\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(True) # keep "\n"
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(True)
## 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, 0)
except StopIteration:
if self.depth > 0:
raise TemplateSyntaxError("unexpected EOF.", (self.filename, len(lines), None, line))
else:
#raise TemplateSyntaxError("unexpected syntax.")
pass
return block
def _parse_lines(self, iter, end_block, block, linenum):
if block is None: block = []
START_WORDS_ = START_WORDS
END_WORDS_ = END_WORDS
CONT_WORDS_ = CONT_WORDS
WORD_REXP_ = _WORD_REXP
while True:
line = iter.next()
linenum += line.count("\n")
m = WORD_REXP_.search(line)
if not m:
block.append(line)
continue
word = m.group(0)
if word in END_WORDS_:
if word != end_block:
msg = "'%s' expected buf got '%s'" % (end_block, word)
colnum = m.start() + 1
raise TemplateSyntaxError(msg, (self.filename, linenum, colnum, line))
return block, line, None, linenum
elif line.endswith(':\n'):
if word in CONT_WORDS_:
return block, line, word, linenum
elif word in START_WORDS_:
block.append(line)
self.depth += 1
cont_word = None
try:
child_block, line, cont_word, linenum = self._parse_lines(iter, '#end'+word, [], linenum)
block.extend((child_block, line, ))
while cont_word: # 'elif' or 'else:'
child_block, line, cont_word, linenum = self._parse_lines(iter, '#end'+word, [], linenum)
block.extend((child_block, line, ))
except StopIteration:
msg = "'%s' is not closed." % (cont_word or word)
colnum = m.start() + 1
raise TemplateSyntaxError(msg, (self.filename, linenum, colnum, 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)
elif line.isspace():
buf.append(line)
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 #endif ?>
<?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
## raise error if syntax is invalid
try:
template.convert(input.replace('#endif', '#endfor'))
except:
ex = sys.exc_info()[1]
assert ex.message == "'#endif' expected buf got '#endfor' (line 8)"
else:
assert "exception expected but not raised"
try:
template.convert(input.replace('#endfor', '#endif'), 'ex.pyhtml')
except:
ex = sys.exc_info()[1]
assert ex.message == "'#endfor' expected buf got '#endif' (ex.pyhtml:9)"
else:
assert "exception expected but not raised"
@samwyse
Copy link

samwyse commented Sep 10, 2010

In the statement '''assert "exception expected but not raised"''', the string evaluates as True, so no assertion error is raised.You probably want to say '''raise AssertionError("exception expected but not raised")''' instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment