mirror of
https://github.com/Hopiu/djLint.git
synced 2026-03-16 21:40:24 +00:00
fix(attributes): prevented attribute indentation for url type attributes
Also fixed attribute quoting issues closes #320, closes #86, closes #195
This commit is contained in:
parent
19136329d7
commit
907a1bfb9c
4 changed files with 107 additions and 190 deletions
|
|
@ -8,19 +8,13 @@ from ..helpers import inside_ignored_block
|
|||
from ..settings import Config
|
||||
|
||||
|
||||
def format_template_tags(config: Config, attributes: str) -> str:
|
||||
def format_template_tags(config: Config, attributes: str, spacing: int) -> 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
|
||||
leading_space = ""
|
||||
|
||||
if re.search(r"^[ ]+", attributes.splitlines()[0], re.MULTILINE):
|
||||
leading_space = re.search(
|
||||
r"^[ ]+", attributes.splitlines()[0], re.MULTILINE
|
||||
).group()
|
||||
|
||||
def add_indentation(config: Config, attributes: str) -> str:
|
||||
def add_indentation(config: Config, attributes: str, spacing: int) -> str:
|
||||
"""Indent template tags.
|
||||
|
||||
| <form class="this"
|
||||
|
|
@ -33,67 +27,35 @@ def format_template_tags(config: Config, attributes: str) -> str:
|
|||
| ^----^ base indent
|
||||
|
|
||||
"""
|
||||
attr_name = (
|
||||
list(
|
||||
re.finditer(
|
||||
re.compile(r"^<\w+\b\s*", re.M), attributes.splitlines()[0].strip()
|
||||
)
|
||||
)
|
||||
)[-1]
|
||||
|
||||
start_test_list = list(
|
||||
re.finditer(
|
||||
re.compile(
|
||||
r"^.*?(?=" + config.template_indent + r")", re.I | re.X | re.M
|
||||
),
|
||||
attributes.splitlines()[0].strip(),
|
||||
)
|
||||
) + list(
|
||||
re.finditer(
|
||||
re.compile(r"^<\w+\b\s*[^\"']+?[\"']", re.M),
|
||||
attributes.splitlines()[0].strip(),
|
||||
)
|
||||
)
|
||||
|
||||
start_test = start_test_list[-1] if start_test_list else None
|
||||
|
||||
base_indent = len(attr_name.group())
|
||||
|
||||
indent = 0
|
||||
indented = ""
|
||||
indent_adder = 0
|
||||
|
||||
# if the "start test" open is actually closed, then ignore the indent.
|
||||
if not re.findall(
|
||||
re.compile(r"[\"']$", re.M), attributes.splitlines()[0].strip()
|
||||
):
|
||||
indent_adder = len(start_test.group()) - base_indent if start_test else 0
|
||||
indent_adder = spacing or 0
|
||||
|
||||
for line_number, line in enumerate(attributes.splitlines()):
|
||||
|
||||
# when checking for template tag, use "match" to force start of line check.
|
||||
if re.match(
|
||||
re.compile(config.template_unindent, re.I | re.X), line.strip()
|
||||
):
|
||||
|
||||
indent = indent - 1
|
||||
tmp = (indent * config.indent) + (indent_adder * " ") + line.strip()
|
||||
|
||||
# if we are leaving an indented group, then remove the indent_adder
|
||||
elif re.match(
|
||||
re.compile(config.tag_unindent_line, re.I | re.X), line.strip()
|
||||
):
|
||||
# if we are leaving an indented group, then remove the indent_adder
|
||||
tmp = (
|
||||
max(indent - 1, 0) * config.indent
|
||||
+ indent_adder * " "
|
||||
+ line.strip()
|
||||
)
|
||||
|
||||
# for open tags, search, but then check that they are not closed.
|
||||
elif re.search(
|
||||
re.compile(config.template_indent, re.I | re.X), line.strip()
|
||||
) and not re.search(
|
||||
re.compile(config.template_unindent, re.I | re.X), line.strip()
|
||||
):
|
||||
# for open tags, search, but then check that they are not closed.
|
||||
tmp = (indent * config.indent) + (indent_adder * " ") + line.strip()
|
||||
indent = indent + 1
|
||||
|
||||
|
|
@ -102,97 +64,28 @@ def format_template_tags(config: Config, attributes: str) -> str:
|
|||
|
||||
if line_number == 0:
|
||||
# don't touch first line
|
||||
indented += f"{leading_space}{line.strip()}"
|
||||
indented += line.strip()
|
||||
else:
|
||||
# if changing indent level and not the first item on the line, then
|
||||
# check if base indent is changed.
|
||||
# match must start at first of string
|
||||
start_test = list(
|
||||
re.finditer(re.compile(r"^(\w+?=[\"'])", re.M), line.strip())
|
||||
) + list(
|
||||
re.finditer(
|
||||
re.compile(
|
||||
r"^(.+?)" + config.template_indent, re.I | re.X | re.M
|
||||
),
|
||||
line.strip(),
|
||||
)
|
||||
)
|
||||
|
||||
if start_test:
|
||||
indent_adder = len(start_test[-1].group(1)) - (
|
||||
base_indent if line_number == 0 else 0
|
||||
)
|
||||
|
||||
base_indent_space = base_indent * " "
|
||||
|
||||
if tmp.strip() != "":
|
||||
indented += f"\n{leading_space}{base_indent_space}{tmp}"
|
||||
|
||||
end_text = re.findall(re.compile(r"[\"']$", re.M), line.strip())
|
||||
|
||||
if end_text:
|
||||
indent_adder = 0
|
||||
indented += f"\n{tmp}"
|
||||
|
||||
return indented
|
||||
|
||||
def add_break(
|
||||
config: Config, attributes: str, pattern: str, match: re.Match
|
||||
) -> str:
|
||||
def add_break(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:
|
||||
# if we don't know where we are, then return what we started with.
|
||||
if not re.findall(
|
||||
re.compile(r"^<\w+[^=\"']\s*", re.M), attributes[: match.start()]
|
||||
):
|
||||
return match.group()
|
||||
|
||||
attr_name = list(
|
||||
re.finditer(
|
||||
re.compile(r"^<\w+[^=\"']\s*", re.M), attributes[: match.start()]
|
||||
)
|
||||
)[-1]
|
||||
|
||||
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{match.group()}"
|
||||
|
||||
# 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{match.group(2).strip()}"
|
||||
|
||||
break_char = config.break_before
|
||||
|
||||
func = partial(add_break, config, attributes, "before")
|
||||
func = partial(add_break, "before")
|
||||
|
||||
attributes = re.sub(
|
||||
re.compile(
|
||||
break_char
|
||||
+ r"\K((?:{%|{{\#)[ ]*?(?:"
|
||||
+ r".\K((?:{%|{{\#)[ ]*?(?:"
|
||||
+ config.break_template_tags
|
||||
+ ")[^}]+?[%|}]})",
|
||||
flags=re.IGNORECASE | re.MULTILINE | re.VERBOSE,
|
||||
|
|
@ -201,7 +94,7 @@ def format_template_tags(config: Config, attributes: str) -> str:
|
|||
attributes,
|
||||
)
|
||||
|
||||
func = partial(add_break, config, attributes, "after")
|
||||
func = partial(add_break, "after")
|
||||
# break after
|
||||
attributes = re.sub(
|
||||
re.compile(
|
||||
|
|
@ -213,34 +106,11 @@ def format_template_tags(config: Config, attributes: str) -> str:
|
|||
func,
|
||||
attributes,
|
||||
)
|
||||
|
||||
attributes = add_indentation(config, attributes)
|
||||
attributes = add_indentation(config, attributes, spacing)
|
||||
|
||||
return attributes
|
||||
|
||||
|
||||
def format_style(match: re.match) -> str:
|
||||
"""Format inline styles."""
|
||||
tag = match.group(2)
|
||||
|
||||
quote = match.group(3)
|
||||
|
||||
# if the style attrib is following the tag name
|
||||
leading_stuff = (
|
||||
match.group(1)
|
||||
if not bool(re.match(r"^\s+$", match.group(1), re.MULTILINE))
|
||||
else len(match.group(1)) * " "
|
||||
)
|
||||
|
||||
spacing = "\n" + len(match.group(1)) * " " + len(tag) * " " + len(quote) * " "
|
||||
|
||||
styles = (spacing).join(
|
||||
[x.strip() + ";" for x in match.group(4).split(";") if x.strip()]
|
||||
)
|
||||
|
||||
return f"{leading_stuff}{tag}{quote}{styles}{quote}"
|
||||
|
||||
|
||||
def format_attributes(config: Config, html: str, match: re.match) -> str:
|
||||
"""Spread long attributes over multiple lines."""
|
||||
# check that we are not inside an ignored block
|
||||
|
|
@ -254,38 +124,68 @@ def format_attributes(config: Config, html: str, match: re.match) -> str:
|
|||
|
||||
tag = match.group(2) + " "
|
||||
|
||||
spacing = "\n" + leading_space + len(tag) * " "
|
||||
spacing = leading_space + len(tag) * " "
|
||||
|
||||
attributes = []
|
||||
|
||||
# format attributes as groups
|
||||
attributes = (spacing).join(
|
||||
[
|
||||
x.group()
|
||||
for x in re.finditer(
|
||||
config.attribute_pattern, match.group(3).strip(), re.VERBOSE
|
||||
for attr_grp in re.finditer(
|
||||
config.attribute_pattern, match.group(3).strip(), re.VERBOSE
|
||||
):
|
||||
|
||||
attrib_name = attr_grp.group(1)
|
||||
is_quoted = attr_grp.group(2) and attr_grp.group(2)[0] in ["'", '"']
|
||||
quote = attr_grp.group(2)[0] if is_quoted else '"'
|
||||
attrib_value = attr_grp.group(2).strip("\"'") if attr_grp.group(2) else None
|
||||
standalone = attr_grp.group(3)
|
||||
|
||||
quote_length = 1
|
||||
|
||||
if attrib_name and attrib_value:
|
||||
# for the equals sign
|
||||
quote_length += 1
|
||||
|
||||
# format style attribute
|
||||
if attrib_name and attrib_name.lower() == "style":
|
||||
if config.format_attribute_template_tags:
|
||||
join_space = "\n" + spacing
|
||||
else:
|
||||
join_space = (
|
||||
"\n" + spacing + int(quote_length + len(attrib_name or "")) * " "
|
||||
)
|
||||
attrib_value = (";" + join_space).join(
|
||||
[value.strip() for value in attrib_value.split(";") if value.strip()]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# format template stuff
|
||||
if config.format_attribute_template_tags:
|
||||
if attrib_value and attrib_name not in config.ignored_attributes:
|
||||
attrib_value = format_template_tags(
|
||||
config,
|
||||
attrib_value,
|
||||
int(len(spacing) + len(attrib_name or "") + quote_length),
|
||||
)
|
||||
|
||||
if standalone:
|
||||
standalone = format_template_tags(
|
||||
config, standalone, int(len(spacing) + len(attrib_name or ""))
|
||||
)
|
||||
|
||||
if attrib_name and attrib_value or is_quoted:
|
||||
attrib_value = attrib_value or ""
|
||||
attributes.append(f"{attrib_name}={quote}{attrib_value}{quote}")
|
||||
else:
|
||||
attributes.append(
|
||||
(attrib_name or "") + (attrib_value or "") + (standalone or "")
|
||||
)
|
||||
|
||||
attribute_string = ("\n" + spacing).join([x for x in attributes if x])
|
||||
|
||||
close = match.group(4)
|
||||
|
||||
attributes = f"{leading_space}{tag}{attributes}{close}"
|
||||
|
||||
# format template tags
|
||||
if config.format_attribute_template_tags:
|
||||
attributes = format_template_tags(config, attributes)
|
||||
|
||||
# format styles
|
||||
func = partial(format_style)
|
||||
attributes = re.sub(
|
||||
re.compile(
|
||||
config.attribute_style_pattern,
|
||||
re.VERBOSE | re.IGNORECASE | re.M,
|
||||
),
|
||||
func,
|
||||
attributes,
|
||||
)
|
||||
attribute_string = f"{leading_space}{tag}{attribute_string}{close}"
|
||||
|
||||
# clean trailing spaces added by breaks
|
||||
attributes = "\n".join([x.rstrip() for x in attributes.splitlines()])
|
||||
attribute_string = "\n".join([x.rstrip() for x in attribute_string.splitlines()])
|
||||
|
||||
return f"{attributes}"
|
||||
return f"{attribute_string}"
|
||||
|
|
|
|||
|
|
@ -505,11 +505,11 @@ class Config:
|
|||
self.attribute_pattern: str = (
|
||||
rf"""
|
||||
(?:
|
||||
(?:
|
||||
(
|
||||
(?:\w|-|\.)+ | required | checked
|
||||
) # attribute name
|
||||
(?: [ ]*?=[ ]*? # followed by "="
|
||||
(?:
|
||||
(
|
||||
\"[^\"]*? # double quoted attribute
|
||||
(?:
|
||||
{self.template_if_for_pattern} # if or for loop
|
||||
|
|
@ -526,21 +526,32 @@ class Config:
|
|||
| [^'] # anything else
|
||||
)*?
|
||||
\' # closing quote
|
||||
| (?:\w|-)+ # or a non-quoted value
|
||||
| (?:\w|-)+ # or a non-quoted string value
|
||||
| {{{{.*?}}}} # a non-quoted template var
|
||||
| {{%[^}}]*?%}} # a non-quoted template tag
|
||||
| {self.template_if_for_pattern} # a non-quoted if statement
|
||||
|
||||
)
|
||||
)? # attribute value
|
||||
)
|
||||
| {self.template_if_for_pattern}
|
||||
| ({self.template_if_for_pattern}
|
||||
"""
|
||||
+ r"""
|
||||
| {{.*?}}
|
||||
| {%.*?%}
|
||||
| {%.*?%})
|
||||
"""
|
||||
)
|
||||
|
||||
self.attribute_style_pattern: str = r"^(.*?)(style=)([\"|'])(([^\"']+?;)+?)\3"
|
||||
|
||||
self.ignored_attributes = [
|
||||
"href",
|
||||
"action",
|
||||
"data-url",
|
||||
"src",
|
||||
"url",
|
||||
"srcset",
|
||||
"data-src",
|
||||
]
|
||||
self.start_template_tags: str = (
|
||||
r"""
|
||||
if
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@
|
|||
{% else %}
|
||||
that is long stuff asdf and more even
|
||||
{% endif %}"/>
|
||||
<img data-src="{% if report.imgs.exists %}
|
||||
{{ report.imgs.first.get_absolute_url|size:"96x96" }}
|
||||
{% else %}
|
||||
{% static '/img/report_thumb_placeholder_400x300.png' %}
|
||||
{% endif %}"
|
||||
<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"/>
|
||||
<a class="asdf
|
||||
|
|
@ -22,8 +18,9 @@
|
|||
Add to Favorites
|
||||
{% endif %}"
|
||||
fav-type="report"
|
||||
object-id="{{ report.report_id }}">
|
||||
<span class="icon has-text-grey is-large ">
|
||||
object-id="{{ report.report_id }}"
|
||||
href="{% if %}{% endif %}">
|
||||
<span class="icon has-text-grey is-large">
|
||||
<i class="fas fa-lg fa-star"></i>
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ def test_long_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
class="class one class two"
|
||||
disabled="true"
|
||||
value="something pretty long goes here"
|
||||
style="width:100px;cursor: text;border:1px solid pink"
|
||||
style="width:100px;
|
||||
cursor: text;
|
||||
border:1px solid pink"
|
||||
required="true"/>
|
||||
"""
|
||||
)
|
||||
|
|
@ -62,7 +64,7 @@ def test_long_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
style="margin-left: 90px;
|
||||
display: contents;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;">
|
||||
font-size: 1.5rem">
|
||||
""",
|
||||
)
|
||||
|
||||
|
|
@ -76,7 +78,7 @@ def test_long_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
<div style="margin-left: 90px;
|
||||
display: contents;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;"
|
||||
font-size: 1.5rem"
|
||||
data-attr="stuff"
|
||||
class="my long class goes here">
|
||||
</div>
|
||||
|
|
@ -85,13 +87,18 @@ def test_long_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
)
|
||||
assert output.exit_code == 0
|
||||
|
||||
# attributes with space around = are not brocken
|
||||
# attributes with space around = are not broken
|
||||
# https://github.com/Riverside-Healthcare/djLint/issues/317
|
||||
# https://github.com/Riverside-Healthcare/djLint/issues/330
|
||||
output = reformat(
|
||||
tmp_file,
|
||||
runner,
|
||||
b"""<a href = "http://test.test:3000/testtesttesttesttesttesttesttesttesttest">Test</a>\n""",
|
||||
)
|
||||
assert output.exit_code == 0
|
||||
assert (
|
||||
output.text
|
||||
== """<a href="http://test.test:3000/testtesttesttesttesttesttesttesttesttest">Test</a>\n"""
|
||||
)
|
||||
|
||||
|
||||
def test_ignored_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
||||
|
|
@ -156,7 +163,7 @@ def test_boolean_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
</select>""",
|
||||
)
|
||||
assert output.exit_code == 1
|
||||
print(output.text)
|
||||
|
||||
assert (
|
||||
output.text
|
||||
== """<select multiple
|
||||
|
|
@ -164,7 +171,9 @@ def test_boolean_attributes(runner: CliRunner, tmp_file: TextIO) -> None:
|
|||
id="device-select"
|
||||
title=""
|
||||
value="something pretty long goes here"
|
||||
style="width:100px;cursor: text;border:1px solid pink">
|
||||
style="width:100px;
|
||||
cursor: text;
|
||||
border:1px solid pink">
|
||||
</select>
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue