mirror of
https://github.com/Hopiu/djLint.git
synced 2026-05-02 10:54:44 +00:00
added formatting for conditions in attributes. closes #66
This commit is contained in:
parent
87a95ea15a
commit
12530b0add
6 changed files with 167 additions and 11 deletions
|
|
@ -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
|
||||
-----
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
9
src/test.py
Normal 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))
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue