Skip to content

Commit

Permalink
Prevent syntax errors when line-wrapping comprehensions
Browse files Browse the repository at this point in the history
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 authored and defnull committed Oct 24, 2015
1 parent ef0623c commit 1f6fda6
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 6 deletions.
26 changes: 20 additions & 6 deletions bottle.py
Original file line number Diff line number Diff line change
Expand Up @@ -3415,15 +3415,19 @@ class StplParser(object):
_re_inl = _re_tok.replace('|\\n','') # We re-use this string pattern later
# 2: Comments (until end of line, but not the newline itself)
_re_tok += '|(#.*)'
# 3,4: Keywords that start or continue a python block (only start of line)
# 3,4: Open and close grouping tokens
_re_tok += '|([\[\{\(])'
_re_tok += '|([\]\}\)])'
# 5,6: Keywords that start or continue a python block (only start of line)
_re_tok += '|^([ \\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)
_re_tok += '|((?:^|;)[ \\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)
_re_tok += '|(%(block_close)s[ \\t]*(?=$))'
# 7: And finally, a single newline. The 8th token is 'everything else'
# 9: And finally, a single newline. The 10th token is 'everything else'
_re_tok += '|(\\r?\\n)'

# Match the start tokens of code areas in a template
_re_split = '(?m)^[ \t]*(\\\\?)((%(line_start)s)|(%(block_start)s))(%%?)'
# Match inline statements (may contain python strings)
Expand All @@ -3437,6 +3441,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: <% %> % {{ }}) '''
Expand Down Expand Up @@ -3493,8 +3498,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
Expand All @@ -3503,6 +3508,15 @@ 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
Expand Down
12 changes: 12 additions & 0 deletions test/test_stpl.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,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 1f6fda6

Please sign in to comment.