## ## 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(('\n', )) ## #endif ## """[1:] ## lines = input.splitlines() ## block = self.parse_lines(lines) ## #=> [ "if items:\n", ## [ "_buf.extend(('\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"""

nothing.

#{i} ${item}

Not found.

"""[1:] expected = r""" _buf.extend((''' \n''', )); if items: _buf.extend((''' \n''', )); i = 0 for item in items: i += 1 klass = i % 2 and 'odd' or 'even' _buf.extend((''' \n''', )); else: _buf.extend(('''

nothing.

\n''', )); #endfor _buf.extend(('''
''', to_str(i), ''' ''', escape(to_str(item)), '''
\n''', )); else: _buf.extend(('''

Not found.

\n''', )); #endif _buf.extend((''' \n''', )); """[1:] # template = MyTemplate() actual = template.convert(input) assert expected == actual #print(actual) #import pprint #pprint.pprint(result) ## if-statement input = r""" 0: ?>

Positive.

Negative.

Zero.

"""[1:] expected = r""" for x in nums: if x > 0: _buf.extend(('''

Positive.

\n''', )); elif x < 0: _buf.extend(('''

Negative.

\n''', )); else: _buf.extend(('''

Zero.

\n''', )); #endif #endfor """[1:] actual = template.convert(input) assert expected == actual