Created
June 13, 2009 15:49
-
-
Save kwatch/129297 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
## 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" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.