zulip/tools/lib/pretty_print.py
adnrs96 a75c0dd248 Enhance template parser for comments, Fix deep nesting in Jinja2.
In this commit we add the ability of recognizing comments in
handlebar and Jinja2 templates to the template parser. This
fixes issue of template parser picking up code tags which are
commented out in files.
Also we fix the problem of too much deep nesting in the Jinja2
logic statement. Now only nested Jinja2 logic statements will get
a deeper indentation level.With this another fix was introduced
relating with the tags before a nested handlebar or django tag getting
wrong indent.
We also fix the issue with wrong offsets with closing tags in
cases if they were not matching indent level of the starting
tag intially.
Also we also start to ignore any tags occuring in between 'pre'
tags for deeper indent levels. As well we start to filter out django
non block tags from getting deeper indent level.
2017-03-11 15:16:44 -08:00

170 lines
7.8 KiB
Python

from __future__ import absolute_import
from __future__ import print_function
from typing import Any, Dict, List
from .template_parser import (
tokenize,
Token,
is_django_block_tag,
)
from six.moves import range
def pretty_print_html(html, num_spaces=4):
# type: (str, int) -> str
# We use 1-based indexing for both rows and columns.
tokens = tokenize(html)
lines = html.split('\n')
# We will keep a stack of "start" tags so that we know
# when HTML ranges end. Note that some start tags won't
# be blocks from an indentation standpoint.
stack = [] # type: List[Dict[str, Any]]
# Seed our stack with a pseudo entry to make depth calculations
# easier.
info = dict(
block=False,
depth=-1,
line=-1,
token_kind='html_start',
tag='html',
extra_indent=0)
stack.append(info)
# Our main job is to figure out offsets that we use to nudge lines
# over by.
offsets = {} # type: Dict[int, int]
# Loop through our start/end tokens, and calculate offsets. As
# we proceed, we will push/pop info dictionaries on/off a stack.
for token in tokens:
if token.kind in ('html_start', 'handlebars_start',
'html_singleton', 'django_start') and stack[-1]['tag'] != 'pre':
# An HTML start tag should only cause a new indent if we
# are on a new line.
if (token.tag not in ('extends', 'include', 'else', 'elif') and
(is_django_block_tag(token.tag) or
token.kind != 'django_start')):
is_block = token.line > stack[-1]['line']
if is_block:
if (((token.kind == 'handlebars_start' and
stack[-1]['token_kind'] == 'handlebars_start') or
(token.kind == 'django_start' and
stack[-1]['token_kind'] == 'django_start')) and
not stack[-1]['indenting']):
info = stack.pop()
info['depth'] = info['depth'] + 1
info['indenting'] = True
info['adjust_offset_until'] = token.line
stack.append(info)
new_depth = stack[-1]['depth'] + 1
extra_indent = stack[-1]['extra_indent']
line = lines[token.line - 1]
adjustment = len(line)-len(line.lstrip()) + 1
offset = (1 + extra_indent + new_depth * num_spaces) - adjustment
info = dict(
block=True,
depth=new_depth,
actual_depth=new_depth,
line=token.line,
tag=token.tag,
token_kind=token.kind,
line_span=token.line_span,
offset=offset,
extra_indent=token.col - adjustment + extra_indent,
extra_indent_prev=extra_indent,
adjustment=adjustment,
indenting=True,
adjust_offset_until=token.line
)
if token.kind in ('handlebars_start', 'django_start'):
info.update(dict(depth=new_depth - 1, indenting=False))
else:
info = dict(
block=False,
depth=stack[-1]['depth'],
actual_depth=stack[-1]['depth'],
line=token.line,
tag=token.tag,
token_kind=token.kind,
extra_indent=stack[-1]['extra_indent']
)
stack.append(info)
elif token.kind in ('html_end', 'handlebars_end',
'html_singleton_end', 'django_end') and (stack[-1]['tag'] != 'pre' or token.tag == 'pre'):
info = stack.pop()
if info['block']:
# We are at the end of an indentation block. We
# assume the whole block was formatted ok before, just
# possibly at an indentation that we don't like, so we
# nudge over all lines in the block by the same offset.
start_line = info['line']
end_line = token.line
if token.tag == 'pre':
offsets[start_line] = 0
offsets[end_line] = 0
else:
offsets[start_line] = info['offset']
line = lines[token.line - 1]
adjustment = len(line)-len(line.lstrip()) + 1
if adjustment == token.col:
offsets[end_line] = (info['offset'] +
info['adjustment'] -
adjustment +
info['extra_indent'] -
info['extra_indent_prev'])
elif (start_line + info['line_span'] - 1 == end_line and
info['line_span'] > 2 and token.kind != 'html_singleton_end'):
offsets[end_line] = (1 + info['extra_indent'] + (info['depth'] + 1) * num_spaces) - adjustment
elif token.line != info['line']:
offsets[end_line] = info['offset']
if token.tag != 'pre' and token.kind != 'html_singleton_end' and token.tag != 'script':
for line_num in range(start_line + 1, end_line):
# Be careful not to override offsets that happened
# deeper in the HTML within our block.
if line_num not in offsets:
line = lines[line_num - 1]
new_depth = info['depth'] + 1
if (line.lstrip().startswith('{{else}}') or
line.lstrip().startswith('{% else %}') or
line.lstrip().startswith('{% elif')):
new_depth = info['actual_depth']
extra_indent = info['extra_indent']
adjustment = len(line)-len(line.lstrip()) + 1
offset = (1 + extra_indent + new_depth * num_spaces) - adjustment
offsets[line_num] = offset
elif (token.kind in ('handlebars_end', 'django_end') and
info['indenting'] and
line_num < info['adjust_offset_until']):
offsets[line_num] += num_spaces
elif token.tag != 'pre':
for line_num in range(start_line + 1, end_line):
if line_num not in offsets:
offsets[line_num] = info['offset']
else:
for line_num in range(start_line + 1, end_line):
if line_num not in offsets:
offsets[line_num] = 0
# Now that we have all of our offsets calculated, we can just
# join all our lines together, fixing up offsets as needed.
formatted_lines = []
for i, line in enumerate(html.split('\n')):
row = i + 1
offset = offsets.get(row, 0)
pretty_line = line
if line.strip() == '':
pretty_line = ''
else:
if offset > 0:
pretty_line = (' ' * offset) + pretty_line
elif offset < 0:
pretty_line = pretty_line[-1 * offset:]
assert line.strip() == pretty_line.strip()
formatted_lines.append(pretty_line)
return '\n'.join(formatted_lines)