Skip to content
Permalink
Browse files

Prevent syntax errors when line-wrapping comprehensions

Ignores block keywords inside parentheses/braces/brackets, allowing
list/set/dict comprehensions to be wrapped in the same way as dicts. Does not
check for correctly paired parentheses - we can let python do that, right?

Builds upon cleanup made in #689
  • Loading branch information...
eric-wieser committed Dec 26, 2014
1 parent db0262e commit 8a5bfe2f70a4a5d22b93e909b207584324817760
Showing with 33 additions and 6 deletions.
  1. +21 −6 bottle.py
  2. +12 −0 test/test_stpl.py
@@ -3351,18 +3351,22 @@ class StplParser(object):
_re_tok += r'''
# 2: Comments (until end of line, but not the newline itself)
|(\#.*)
# 3: Open and close (4) grouping tokens
|([\[\{\(])
|([\]\}\)])
# 3,4: Keywords that start or continue a python block (only start of line)
# 5,6: Keywords that start or continue a python block (only start of line)
|^([\ \t]*(?:if|for|while|with|try|def|class)\b)
|^([\ \t]*(?:elif|else|except|finally)\b)
# 5: Our special 'end' keyword (but only if it stands alone)
# 7: Our special 'end' keyword (but only if it stands alone)
|((?:^|;)[\ \t]*end[\ \t]*(?=(?:%(block_close)s[\ \t]*)?\r?$|;|\#))
# 6: A customizable end-of-code-block template token (only end of line)
# 8: A customizable end-of-code-block template token (only end of line)
|(%(block_close)s[\ \t]*(?=\r?$))
# 7: And finally, a single newline. The 8th token is 'everything else'
# 9: And finally, a single newline. The 10th token is 'everything else'
|(\r?\n)
'''
# Match the start tokens of code areas in a template
@@ -3378,6 +3382,7 @@ def __init__(self, source, syntax=None, encoding='utf8'):
self.code_buffer, self.text_buffer = [], []
self.lineno, self.offset = 1, 0
self.indent, self.indent_mod = 0, 0
self.paren_depth = 0

def get_syntax(self):
""" Tokens as a space separated string (default: <% %> % {{ }}) """
@@ -3429,8 +3434,8 @@ def read_code(self, multiline):
return
code_line += self.source[self.offset:self.offset+m.start()]
self.offset += m.end()
_str, _com, _blk1, _blk2, _end, _cend, _nl = m.groups()
if code_line and (_blk1 or _blk2): # a if b else c
_str, _com, _po, _pc, _blk1, _blk2, _end, _cend, _nl = m.groups()
if (code_line or self.paren_depth > 0) and (_blk1 or _blk2): # a if b else c
code_line += _blk1 or _blk2
continue
if _str: # Python string
@@ -3439,6 +3444,16 @@ def read_code(self, multiline):
comment = _com
if multiline and _com.strip().endswith(self._tokens[1]):
multiline = False # Allow end-of-block in comments
elif _po: # open parenthesis
self.paren_depth += 1
code_line += _po
elif _pc: # close parenthesis
if self.paren_depth > 0:
# we could check for matching parentheses here, but it's
# easier to leave that to python - just check counts
self.paren_depth -= 1
code_line += _pc

elif _blk1: # Start-block keyword (if/for/while/def/try/...)
code_line, self.indent_mod = _blk1, -1
self.indent += 1
@@ -336,6 +336,18 @@ def test_multiline_strings_in_code_line(self):
'''
self.assertRenders(source, result)

def test_multiline_comprehensions_in_code_line(self):
self.assertRenders(source='''
% a = [
% (i + 1)
% for i in range(5)
% if i%2 == 0
% ]
{{a}}
''', result='''
[1, 3, 5]
''')

if __name__ == '__main__': #pragma: no cover
unittest.main()

0 comments on commit 8a5bfe2

Please sign in to comment.
You can’t perform that action at this time.