added formatting for conditions in attributes. closes #66

This commit is contained in:
Christopher Pickering 2021-10-06 13:17:04 +02:00
parent 87a95ea15a
commit 12530b0add
No known key found for this signature in database
GPG key ID: E14DB3B0A0FACF84
6 changed files with 167 additions and 11 deletions

View file

@ -9,6 +9,7 @@ Next Release
- Fixed django ``trans`` tag formatting
- Added linter rule H021 to check for inline styles
- Added formatting for inline styles
- Added formatting for template conditions inside attributes
0.5.3
-----

View file

@ -7,6 +7,97 @@ import regex as re
from ..settings import Config
def format_template_tags(config: Config, attributes: str) -> str:
"""Format template tags in attributes."""
# find break tags, add breaks + indent
# find unindent lines and move back
# put short stuff back on one line
def add_break(
config: Config, attributes: str, pattern: str, match: re.Match
) -> str:
"""Make a decision if a break should be added."""
# check if we are inside an attribute.
inside_attribute = any(
x.start() <= match.start() and match.end() <= x.end()
for x in re.finditer(
re.compile(
r"[a-zA-Z-_]+[ ]*?=[ ]*?([\"'])([^\1]*?"
+ config.template_if_for_pattern
+ r"[^\1]*?)\1",
re.I | re.M | re.X | re.DOTALL,
),
attributes,
)
)
if inside_attribute:
attr_name = list(
re.finditer(
re.compile(r"^.+?\w+[ ]*?=[ ]*?[\"|']", re.M),
attributes[: match.start()],
)
)[-1]
else:
attr_name = list(
re.finditer(
re.compile(r"^<\w+[^=\"']\s*", re.M), attributes[: match.start()]
)
)[-1]
leading_space = len(attr_name.group()) * " "
indent = ""
if re.match(
re.compile(config.tag_indent, re.I | re.X), match.group()
) or re.match(re.compile(config.tag_unindent_line, re.I | re.X), match.group()):
# if an indent tag, then add leading space
indent = config.indent
if pattern == "before":
# but don't add break if we are the first thing in an attribute.
if attr_name.end() == match.start():
return match.group()
return f"\n{leading_space}{match.group()}"
# else "after"
# but don't add a break if the next char closes the attr.
if re.match(r"\s*?[\"|'|>]", match.group(2)):
return match.group(1) + match.group(2)
return f"{match.group(1)}\n{leading_space}{indent}{match.group(2).strip()}"
break_char = config.break_before
func = partial(add_break, config, attributes, "before")
attributes = re.sub(
re.compile(
break_char
+ r"\K((?:{%|{{\#)[ ]*?(?:"
+ config.break_template_tags
+ ")[^}]+?[%|}]})",
flags=re.IGNORECASE | re.MULTILINE | re.VERBOSE,
),
func,
attributes,
)
func = partial(add_break, config, attributes, "after")
# break after
attributes = re.sub(
re.compile(
r"((?:{%|{{\#)[ ]*?(?:"
+ config.break_template_tags
+ ")[^}]+?[%|}]})([^\n]+)$",
flags=re.IGNORECASE | re.MULTILINE | re.VERBOSE,
),
func,
attributes,
)
return attributes
def format_style(match: re.match) -> str:
"""Format inline styles."""
tag = match.group(2)
@ -57,4 +148,7 @@ def format_attributes(config: Config, match: re.match) -> str:
attributes,
)
# format template tags
attributes = format_template_tags(config, attributes)
return f"{attributes}"

View file

@ -64,7 +64,7 @@ def expand_html(html: str, config: Config) -> str:
add_left = partial(add_html_line, "\n%s")
add_right = partial(add_html_line, "%s\n")
break_char = r"(?<!\n[ ]*?)"
break_char = config.break_before
html = re.sub(
re.compile(

View file

@ -380,6 +380,7 @@ class Config:
)
"""
self.break_before = r"(?<!\n[ ]*?)"
# reduce empty lines greater than x to 1 line
self.reduce_extralines_gt = 2
@ -395,22 +396,22 @@ class Config:
# 4. attributes="normal html"
# 5. require | checked | otherword | other-word
# 6. {{ stuff }}
template_if_for_pattern = (
self.template_if_for_pattern = (
r"(?:{%-?\s?(?:if|for)[^}]*?%}(?:.*?{%\s?end(?:if|for)[^}]*?-?%})+?)"
)
self.attribute_pattern: str = (
r"""
(?:[^\s]+?=(?:\"[^\"]*?"""
+ template_if_for_pattern
+ self.template_if_for_pattern
+ r"""[^\"]*?\"|\'[^\']*?"""
+ template_if_for_pattern
+ self.template_if_for_pattern
+ r"""[^\']*?\'))
| (?:[^\s]+?=(?:\"[^\"]*?{{.*?}}[^\"]*?\"|\'[^\']*?{{.*?}}[^\']*?\'))
| """
+ template_if_for_pattern
+ self.template_if_for_pattern
+ r"""
| (?:[^\s]+?=(?:\"(?:[^\"]*?{%[^}]*?%}[^\"]*?)+?\"))
| (?:[^\s]+?=(?:\"(?:[^\']*?{%[^}]*?%}[^\"]*?)+?\'))
| (?:[^\s]+?=(?:\'(?:[^\']*?{%[^}]*?%}[^\']*?)+?\'))
| (?:[^\s]+?=(?:\".*?\"|\'.*?\'))
| required
| checked

9
src/test.py Normal file
View file

@ -0,0 +1,9 @@
import regex as re
string = """<span title="{% if eev.status == eev.STATUS_CURRENT %} {% trans 'A' %} {% elif eev.status == eev.STATUS_APPROVED %} {% trans 'B' %} {% elif eev.status == eev.STATUS_EXPIRED %} {% trans 'C' %}{% endif %}" class="asdf {%if a%}b{%endif%} asdf" {%if a%}checked{%endif%}>"""
regex = r"""(?<!\n[ ]*?)\K(<span[ ]+?(
(?:[^\s]+?=(?:\'(?:[^\']*?{%[^}]*?%}[^\']*?)+?\'))
)\s*?>)"""
print(re.findall(regex, string, re.I | re.X))

View file

@ -7,7 +7,7 @@ run::
for a single test, run::
pytest tests/test_django.py::test_multiple_endblocks --cov=src/djlint \
pytest tests/test_django.py::test_complex_attributes --cov=src/djlint \
--cov-branch --cov-report xml:coverage.xml --cov-report term-missing
"""
@ -290,9 +290,14 @@ def test_complex_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
b"""<img data-src="{% if report.imgs.exists %}{{ report.imgs.first.get_absolute_url|size:"96x96"}}{% else %}{% static '/img/report_thumb_placeholder_400x300.png' %}{% endif %}" src="{% static '/img/loader.gif' %}" alt="report image"/>""",
)
assert output["exit_code"] == 1
assert (
output["text"]
== r"""<img data-src="{% if report.imgs.exists %}{{ report.imgs.first.get_absolute_url|size:"96x96" }}{% else %}{% static '/img/report_thumb_placeholder_400x300.png' %}{% endif %}"
== r"""<img data-src="{% if report.imgs.exists %}
{{ report.imgs.first.get_absolute_url|size:"96x96" }}
{% else %}
{% static '/img/report_thumb_placeholder_400x300.png' %}
{% endif %}"
src="{% static '/img/loader.gif' %}"
alt="report image"/>
"""
@ -304,10 +309,22 @@ def test_complex_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
b"""<a class=" {% if favorite == "yes" %}favorite{% endif %} has-tooltip-arrow has-tooltip-right" data-tooltip=" {% if favorite == "yes" %}Remove from Favorites {% else %}Add to Favorites{% endif %}" fav-type="report" object-id="{{ report.report_id }}"><span class="icon has-text-grey is-large "><i class="fas fa-lg fa-star"></i></span></a>""",
)
assert output["exit_code"] == 1
assert (
output["text"]
== r"""<a class=" {% if favorite == "yes" %}favorite{% endif %} has-tooltip-arrow has-tooltip-right"
data-tooltip=" {% if favorite == "yes" %}Remove from Favorites {% else %}Add to Favorites{% endif %}"
== r"""<a class=" """
+ """
{% if favorite == "yes" %}
favorite
{% endif %}
has-tooltip-arrow has-tooltip-right"
data-tooltip=" """
+ """
{% if favorite == "yes" %}
Remove from Favorites
{% else %}
Add to Favorites
{% endif %}"
fav-type="report"
object-id="{{ report.report_id }}">
<span class="icon has-text-grey is-large ">
@ -322,10 +339,15 @@ def test_complex_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
b"""<div class="media-content" {% ifchanged comment.stream_id %} comments-msg {% else %} comments-newMsgReply {% endifchanged %}>""",
)
assert output["exit_code"] == 1
assert (
output["text"]
== r"""<div class="media-content"
{% ifchanged comment.stream_id %} comments-msg {% else %} comments-newMsgReply {% endifchanged %}>
{% ifchanged comment.stream_id %}
comments-msg
{% else %}
comments-newMsgReply
{% endifchanged %}>
"""
)
output = reformat(
@ -341,6 +363,35 @@ def test_complex_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
)
assert output["exit_code"] == 1
output = reformat(
tmp_file,
runner,
b"""<span {%if a%}required{%endif%}title="{% if eev.status == eev.STATUS_CURRENT %} {% trans 'A' %} {% elif eev.status == eev.STATUS_APPROVED %} {% trans 'B' %} {% elif eev.status == eev.STATUS_EXPIRED %} {% trans 'C' %}{% endif %}" class="asdf {%if a%}b{%endif%} asdf" {%if a%}checked{%endif%}>""",
)
assert (
output["text"]
== """<span {% if a %}
required
{% endif %}
title="{% if eev.status == eev.STATUS_CURRENT %}
{% trans 'A' %}
{% elif eev.status == eev.STATUS_APPROVED %}
{% trans 'B' %}
{% elif eev.status == eev.STATUS_EXPIRED %}
{% trans 'C' %}
{% endif %}"
class="asdf
{% if a %}
b
{% endif %}
asdf"
{% if a %}
checked
{% endif %}>
"""
)
assert output["exit_code"] == 1
def test_load_tag(runner: CliRunner, tmp_file: TextIO) -> None:
output = reformat(