changed build system to poetry, added more dj tags, added pyproject.toml support, fixed #11, closed #12, closed #1

This commit is contained in:
Christopher Pickering 2021-09-08 10:46:18 +02:00
parent 37d8d48cb6
commit 94c1dd5958
No known key found for this signature in database
GPG key ID: E14DB3B0A0FACF84
8 changed files with 694 additions and 374 deletions

View file

@ -21,6 +21,7 @@ import sys
from concurrent.futures import ProcessPoolExecutor, as_completed
from functools import partial
from pathlib import Path
from typing import List
import click
from click import echo
@ -29,22 +30,21 @@ from tqdm import tqdm
from .lint import lint_file
from .reformat import reformat_file
from .settings import ignored_paths
from .settings import Config
def get_src(src: Path, extension=None):
def get_src(src: Path, config: Config) -> List[Path]:
"""Get source files."""
if Path.is_file(src):
return [src]
# remove leading . from extension
extension = str(config.extension)
extension = extension[1:] if extension.startswith(".") else extension
paths = list(
filter(
lambda x: not re.search(
"|".join([re.escape(x) for x in ignored_paths]), str(x)
),
lambda x: not re.search(config.ignored_paths, str(x), re.VERBOSE),
list(src.glob(r"**/*.%s" % extension)),
)
)
@ -56,7 +56,7 @@ def get_src(src: Path, extension=None):
return paths
def build_output(error):
def build_output(error: dict) -> int:
"""Build output for file errors."""
errors = sorted(list(error.values())[0], key=lambda x: int(x["line"].split(":")[0]))
width, _ = shutil.get_terminal_size()
@ -75,10 +75,9 @@ def build_output(error):
)
for message in errors:
error = bool(message["code"][:1] == "E")
echo(
"{} {} {} {} {}".format(
(Fore.RED if error else Fore.YELLOW),
(Fore.RED if bool(message["code"][:1] == "E") else Fore.YELLOW),
message["code"] + Style.RESET_ALL,
Fore.BLUE + message["line"] + Style.RESET_ALL,
message["message"],
@ -89,7 +88,7 @@ def build_output(error):
return len(errors)
def build_check_output(errors, quiet):
def build_check_output(errors: dict, quiet: bool) -> int:
"""Build output for reformat check."""
if len(errors) == 0:
return 0
@ -128,12 +127,12 @@ def build_check_output(errors, quiet):
return len(list(filter(lambda x: len(x) > 0, errors.values())))
def build_quantity(size: int):
def build_quantity(size: int) -> str:
"""Count files in a list."""
return "%d file%s" % (size, ("s" if size > 1 or size == 0 else ""))
def build_quantity_tense(size: int):
def build_quantity_tense(size: int) -> str:
"""Count files in a list."""
return "%d file%s %s" % (
size,
@ -155,7 +154,7 @@ def build_quantity_tense(size: int):
"-e",
"--extension",
type=str,
default="html",
default="",
help="File extension to lint",
show_default=True,
)
@ -184,9 +183,11 @@ def build_quantity_tense(size: int):
)
def main(
src: str, extension: str, ignore: str, reformat: bool, check: bool, quiet: bool
):
) -> None:
"""Djlint django template files."""
file_list = get_src(Path(src), extension)
config = Config(src, extension=extension, ignore=ignore, quiet=quiet)
file_list = get_src(Path(src), config)
if len(file_list) == 0:
return
@ -225,13 +226,13 @@ def main(
with ProcessPoolExecutor(max_workers=worker_count) as exe:
file_errors = []
if reformat is True or check is True:
func = partial(reformat_file, check)
func = partial(reformat_file, config, check)
futures = {
exe.submit(func, this_file): this_file for this_file in file_list
}
else:
func = partial(lint_file, ignore)
func = partial(lint_file, config)
futures = {
exe.submit(func, this_file): this_file for this_file in file_list
}

View file

@ -1,21 +1,11 @@
"""djLint Attempt to logically shrink html."""
import re as old_re
import regex as re
from ..settings import (
ignored_block_closing,
ignored_block_opening,
ignored_group_closing,
ignored_group_opening,
ignored_inline_blocks,
single_line_html_tags,
single_line_template_tags,
start_template_tags,
)
from ..settings import Config
def _clean_line(line):
def _clean_line(line: str) -> str:
"""Clean up a line of html.
* remove duplicate spaces
@ -25,7 +15,7 @@ def _clean_line(line):
return re.sub(r" {2,}", " ", line.strip())
def _strip_html_whitespace(html):
def _strip_html_whitespace(html: str, config: Config) -> str:
"""Remove unnecessary whitespace from text."""
rawcode_flat = ""
is_block_ignored = False
@ -35,17 +25,19 @@ def _strip_html_whitespace(html):
# start of ignored block. If we are already in an ingored block, keep true.
is_group_ignored = is_group_ignored or bool(
re.search("|".join(ignored_group_opening), item, re.IGNORECASE)
re.search(config.ignored_group_opening, item, re.IGNORECASE | re.VERBOSE)
)
# find ignored blocks and retain indentation, otherwise strip white space
if re.findall(
r"(?:%s)" % "|".join(ignored_inline_blocks), item, flags=re.IGNORECASE
r"(?:%s)" % config.ignored_inline_blocks,
item,
flags=re.IGNORECASE | re.VERBOSE,
):
tmp = _clean_line(item)
elif (
re.search("|".join(ignored_block_closing), item, re.IGNORECASE)
re.search(config.ignored_block_closing, item, re.IGNORECASE | re.VERBOSE)
and is_group_ignored is False
):
# do not format ignored lines
@ -53,7 +45,7 @@ def _strip_html_whitespace(html):
is_block_ignored = False
elif (
re.search("|".join(ignored_block_opening), item, re.IGNORECASE)
re.search(config.ignored_block_opening, item, re.IGNORECASE | re.VERBOSE)
and is_group_ignored is False
):
# do not format ignored lines
@ -67,7 +59,9 @@ def _strip_html_whitespace(html):
tmp = _clean_line(item)
# end of ignore raw code
if bool(re.search("|".join(ignored_group_closing), item, re.IGNORECASE)):
if bool(
re.search(config.ignored_group_closing, item, re.IGNORECASE | re.VERBOSE)
):
is_group_ignored = False
rawcode_flat = rawcode_flat + tmp + "\n"
@ -75,10 +69,10 @@ def _strip_html_whitespace(html):
return rawcode_flat
def compress_html(html):
def compress_html(html: str, config: Config) -> str:
"""Compress back tags that do not need to be expanded."""
# put empty tags on one line
html = _strip_html_whitespace(html)
html = _strip_html_whitespace(html, config)
html = re.sub(
r"(<([\w]+)[^>]*>)\s+?(<\/\2>)",
@ -89,22 +83,26 @@ def compress_html(html):
# put empty template tags on one line
html = re.sub(
r"(" + start_template_tags + r")\s+?(\{\% end[^}]*?\%\})",
r"\1\2",
r"({%[ ]*?("
+ re.sub(r"\s", "", config.start_template_tags)
+ r")[^}]+?%})\s+?(\{\% end[^}]*?\%\})",
r"\1\3",
html,
re.MULTILINE,
)
# put short single line tags on one line
slt_html = "|".join(single_line_html_tags)
html = old_re.sub(
# verbose doesn't seem to work with replace groups.
slt_html = re.sub(r"\s", "", config.single_line_html_tags)
html = re.sub(
r"(<(%s)>)\s*([^<\n]{,80})\s*?(</(\2)>)" % slt_html,
r"\1\3\4",
html,
re.IGNORECASE | re.MULTILINE | re.DOTALL,
)
html = old_re.sub(
html = re.sub(
r"(<(%s)>)\s*?([^<\n]{,80})\s*?(</(\2)>)" % slt_html,
r"\1\3\4",
html,
@ -112,17 +110,17 @@ def compress_html(html):
)
html = re.sub(
r"(<(%s) [^\n]{,80}>)\s*([^<\n]{,80})\s*?(</(\2)>)" % slt_html,
r"(<(%s)[ ][^\n]{,80}>)\s*([^<\n]{,80})\s*?(</(\2)>)" % slt_html,
r"\1\3\4",
html,
re.IGNORECASE | re.MULTILINE | re.DOTALL,
)
slt_template = "|".join(single_line_template_tags)
# cannot use verbose when replacing with var. 🤕
html = re.sub(
r"({% +?("
+ slt_template
+ r") +?[^\n]{,30}%})\s*([^%\n]{,50})\s*?({% +?end(\2) +?%})",
r"({%[ ]*?("
+ re.sub(r"\s", "", config.single_line_template_tags)
+ r")[ ]+?[^\n]{,30}%})\s*([^%\n]{,50})\s*?({%[ ]+?end(\2)[ ]*?%})",
r"\1\3\4",
html,
re.IGNORECASE | re.MULTILINE,

View file

@ -4,73 +4,70 @@ from functools import partial
import regex as re
from ..settings import (
break_html_tags,
break_template_tags,
ignored_attributes,
ignored_blocks,
tag_pattern,
)
from ..settings import Config
def _flatten_attributes(match):
def _flatten_attributes(config: Config, match: re.Match) -> str:
"""Flatten multiline attributes back to one line.
Skip when attribute is ignored.
Attribute name can be in group one or group 2.
for now, skipping if they are anywhere
"""
for attribute in ignored_attributes:
for attribute in config.ignored_attributes:
if attribute in match.group():
return match.group()
return "{} {}{}".format(
match.group(1),
" ".join(match.group(2).strip().splitlines()),
" ".join(x.strip() for x in match.group(2).strip().splitlines()),
match.group(3),
)
def _should_ignore(html, match):
def _should_ignore(config: Config, html: str, match: re.Match) -> bool:
"""Do not add whitespace if the tag is in a non indent block."""
for block in ignored_blocks:
for block in config.ignored_blocks:
return any(
ignored_match.start() < match.start(1)
and ignored_match.end() > match.end(1)
for ignored_match in re.finditer(
block % re.escape(match.group(1)), html, re.DOTALL | re.IGNORECASE
block % re.escape(match.group(1)),
html,
re.DOTALL | re.IGNORECASE | re.VERBOSE,
)
)
return False
def expand_html(html):
def expand_html(html: str, config: Config) -> str:
"""Split single line html into many lines based on tags."""
def add_html_line(out_format, match):
def add_html_line(out_format: str, match: re.Match) -> str:
"""Add whitespace.
Do not add whitespace if the tag is in a non indent block.
"""
if _should_ignore(html, match):
if _should_ignore(config, html, match):
return match.group(1)
return out_format % match.group(1)
# put attributes on one line
func = partial(_flatten_attributes, config)
html = old_re.sub(
tag_pattern,
_flatten_attributes,
config.tag_pattern,
func,
html,
flags=re.IGNORECASE | re.MULTILINE,
flags=re.IGNORECASE | re.MULTILINE | re.VERBOSE,
)
html_tags = "|".join(break_html_tags)
html_tags = config.break_html_tags
# process opening tags ############
# the tag either closes <div>
# the tag either opens <div>
# self closes <img />
# has attributes <div text>
# or has attributes and self closes <img text/>
@ -78,7 +75,7 @@ def expand_html(html):
add_left = partial(add_html_line, "\n%s")
add_right = partial(add_html_line, "%s\n")
break_char = r"(?<!\n *?)"
break_char = r"(?<!\n[ ]*?)"
html = re.sub(
r"%s\K(<(?:%s)>)"
@ -88,62 +85,71 @@ def expand_html(html):
),
add_left,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
# <tag>
html = re.sub(
r"(<(?:%s)>)(?=[^\n])" % html_tags, add_right, html, flags=re.IGNORECASE
r"(<(?:%s)>)(?=[^\n])" % html_tags,
add_right,
html,
flags=re.IGNORECASE | re.VERBOSE,
)
# \n<tag /> and \n<tag/>
html = re.sub(
r"%s\K(<(?:%s) ?/>)"
r"%s\K(<(?:%s)[ ]?/>)"
% (
break_char,
html_tags,
),
add_left,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
# <tag /> and <tag/>
html = re.sub(
r"(<(?:%s) ?/>)(?=[^\n])" % html_tags, add_right, html, flags=re.IGNORECASE
r"(<(?:%s)[ ]?/>)(?=[^\n])" % html_tags,
add_right,
html,
flags=re.IGNORECASE | re.VERBOSE,
)
# \n<tag stuff/>, \n<tag stuff>, \n<tag stuff />
html = re.sub(
r"%s\K(<(?:%s) [^>]*?[^/]>)"
r"%s\K(<(?:%s)[ ][^>]*?[^/]>)"
% (
break_char,
html_tags,
),
add_left,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
# <tag stuff/>, <tag stuff>, <tag stuff />
html = re.sub(
r"(<(?:%s) [^>]*?[^/]>)(?=[^\n])" % html_tags,
r"(<(?:%s)[ ][^>]*?[^/]>)(?=[^\n])" % html_tags,
add_right,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
html = re.sub(
r"%s\K(<(?:%s) [^>]+?/>)"
r"%s\K(<(?:%s)[ ][^>]+?/>)"
% (
break_char,
html_tags,
),
add_left,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
html = re.sub(
r"(<(?:%s) [^>]+?/>)(?=[^\n])" % html_tags, add_right, html, flags=re.IGNORECASE
r"(<(?:%s)[ ][^>]+?/>)(?=[^\n])" % html_tags,
add_right,
html,
flags=re.IGNORECASE | re.VERBOSE,
)
# process closing (break_char, html_tags,)s ######
@ -156,50 +162,57 @@ def expand_html(html):
),
add_left,
html,
flags=re.IGNORECASE,
flags=re.IGNORECASE | re.VERBOSE,
)
html = re.sub(
r"(</(?:%s)>)(?=[^\n])" % html_tags, add_right, html, flags=re.IGNORECASE
r"(</(?:%s)>)(?=[^\n])" % html_tags,
add_right,
html,
flags=re.IGNORECASE | re.VERBOSE,
)
# template tag breaks
def should_i_move_template_tag(out_format, match):
def should_i_move_template_tag(out_format: str, match: re.Match) -> str:
# ensure template tag is not inside an html tag
html_tags = "|".join(break_html_tags)
if _should_ignore(html, match):
if _should_ignore(config, html, match):
return match.group(1)
if not re.findall(
r"\<(?:" + html_tags + r") [^>]*?" + re.escape(match.group(1)) + "$",
r"\<(?:"
+ str(config.break_html_tags)
+ r")[ ][^>]*?"
+ re.escape(match.group(1))
+ "$",
html[: match.end()],
re.MULTILINE,
re.MULTILINE | re.VERBOSE,
):
return out_format % match.group(1)
return match.group(1)
for tag in break_template_tags:
# find all matching tags
# template tags
# break before
html = re.sub(
break_char
+ r"\K((?:{%|{{\#)[ ]*?(?:"
+ re.sub(r"\s", "", config.break_template_tags)
+ ")[^}]+?[%|}]})",
partial(should_i_move_template_tag, "\n%s"),
html,
re.IGNORECASE | re.MULTILINE,
)
html = re.sub(
r"%s\K(%s)"
% (
break_char,
tag,
),
partial(should_i_move_template_tag, "\n%s"),
html,
re.IGNORECASE | re.MULTILINE,
)
html = re.sub(
r"(%s)(?=[^\n])" % tag,
partial(should_i_move_template_tag, "%s\n"),
html,
re.IGNORECASE | re.MULTILINE,
)
# break after
html = re.sub(
r"((?:{%|{{\#)[ ]*?(?:"
+ re.sub(r"\s", "", config.break_template_tags)
+ ")[^}]+?[%|}]})(?=[^\n])",
partial(should_i_move_template_tag, "%s\n"),
html,
re.IGNORECASE | re.MULTILINE,
)
return html

View file

@ -1,27 +1,13 @@
"""djLint add indentation to html."""
from functools import partial
import regex as re
from ..settings import (
always_single_line_html_tags,
attribute_pattern,
break_html_tags,
format_long_attributes,
ignored_block_closing,
ignored_block_opening,
ignored_group_closing,
ignored_group_opening,
ignored_inline_blocks,
indent,
max_line_length,
single_line_template_tags,
tag_indent,
tag_unindent,
tag_unindent_line,
)
from ..settings import Config
def _format_attributes(match):
def _format_attributes(config: Config, match: re.match) -> str:
"""Spread long attributes over multiple lines."""
leading_space = match.group(1)
@ -29,7 +15,9 @@ def _format_attributes(match):
spacing = "\n" + leading_space + len(tag) * " "
attributes = (spacing).join(re.findall(attribute_pattern, match.group(3).strip()))
attributes = (spacing).join(
re.findall(str(config.attribute_pattern), match.group(3).strip(), re.VERBOSE)
)
close = match.group(4)
@ -41,70 +29,92 @@ def _format_attributes(match):
)
def indent_html(rawcode):
def indent_html(rawcode: str, config: Config) -> str:
"""Indent raw code."""
rawcode_flat_list = re.split("\n", rawcode)
indent = config.indent
beautified_code = ""
indent_level = 0
is_block_raw = False
slt_html = "|".join(
break_html_tags
) # here using all tags cause we allow empty tags on one line
slt_html = config.break_html_tags
always_slt_html = "|".join(
always_single_line_html_tags
) # here using all tags cause we allow empty tags on one line
# here using all tags cause we allow empty tags on one line
always_slt_html = config.always_single_line_html_tags
slt_template = "|".join(single_line_template_tags)
# here using all tags cause we allow empty tags on one line
slt_template = config.single_line_template_tags
for item in rawcode_flat_list:
# if a raw tag then start ignoring
if (
re.search("|".join(ignored_group_opening), item, re.IGNORECASE)
) or re.search("|".join(ignored_group_opening), item, re.IGNORECASE):
if re.search(config.ignored_group_opening, item, re.IGNORECASE | re.VERBOSE):
is_block_raw = True
if re.findall(
r"(?:%s)" % "|".join(ignored_inline_blocks), item, flags=re.IGNORECASE
config.ignored_inline_blocks, item, flags=re.IGNORECASE | re.VERBOSE
):
tmp = (indent * indent_level) + item + "\n"
# if a one-line, inline tag, just process it, only if line starts w/ it
elif (
re.findall(r"(<(%s)>)(.*?)(</(\2)>)" % slt_html, item, re.IGNORECASE)
or re.findall(r"(<(%s) .+?>)(.*?)(</(\2)>)" % slt_html, item, re.IGNORECASE)
or re.findall(
r"^({% +?(" + slt_template + r") +?.+?%})(.*?)({% +?end(\2) +?.*?%})",
item,
re.IGNORECASE | re.MULTILINE,
re.findall(
r"(<(%s)>)(.*?)(</(\2)>)" % slt_html, item, re.IGNORECASE | re.VERBOSE
)
or re.findall(r"(<(%s) .*?/>)" % slt_html, item, flags=re.IGNORECASE)
or re.findall(
r"(<(%s) .*?/?>)" % always_slt_html, item, flags=re.IGNORECASE
r"(<(%s)[ ].+?>)(.*?)(</(\2)>)" % slt_html,
item,
re.IGNORECASE | re.VERBOSE,
)
or re.findall(
r"^({%[ ]*?("
+ str(slt_template)
+ r")[ ]+?.+?%})(.*?)({%[ ]+?end(\2)[ ]+?.*?%})",
item,
re.IGNORECASE | re.MULTILINE | re.VERBOSE,
)
or re.findall(
r"(<(%s)[ ].*?/>)" % slt_html, item, flags=re.IGNORECASE | re.VERBOSE
)
or re.findall(
r"(<(%s)[ ].*?/?>)" % always_slt_html,
item,
flags=re.IGNORECASE | re.VERBOSE,
)
) and is_block_raw is False:
tmp = (indent * indent_level) + item + "\n"
# if unindent, move left
elif (
re.search(r"^(?:" + tag_unindent + r")", item, re.IGNORECASE | re.MULTILINE)
re.search(
config.tag_unindent,
item,
re.IGNORECASE | re.MULTILINE | re.VERBOSE,
)
and is_block_raw is False
or re.search("|".join(ignored_block_closing), item, re.IGNORECASE)
or re.search(config.ignored_block_closing, item, re.IGNORECASE | re.VERBOSE)
):
indent_level = max(indent_level - 1, 0)
tmp = (indent * indent_level) + item + "\n"
elif (
re.search(r"^" + tag_unindent_line, item, re.IGNORECASE | re.MULTILINE)
re.search(
r"^" + str(config.tag_unindent_line),
item,
re.IGNORECASE | re.MULTILINE | re.VERBOSE,
)
and is_block_raw is False
):
tmp = (indent * (indent_level - 1)) + item + "\n"
# if indent, move right
elif (
re.search(r"^(?:" + tag_indent + r")", item, re.IGNORECASE | re.MULTILINE)
re.search(
r"^(?:" + str(config.tag_indent) + r")",
item,
re.IGNORECASE | re.MULTILINE | re.VERBOSE,
)
and is_block_raw is False
):
tmp = (indent * indent_level) + item + "\n"
@ -123,7 +133,7 @@ def indent_html(rawcode):
# we can try to fix template tags
tmp = re.sub(r"({[{|%]\-?)(\w[^}].+?)([}|%]})", r"\1 \2\3", tmp)
tmp = re.sub(r"({[{|%])([^}].+?[^(?: |\-)])([}|%]})", r"\1\2 \3", tmp)
tmp = re.sub(r"({[{|%])([^}].+?[^(?:\ |\-)])([}|%]})", r"\1\2 \3", tmp)
tmp = re.sub(r"({[{|%])([^}].+?[^ ])(\-[}|%]})", r"\1\2 \3", tmp)
# handlebars templates
@ -132,23 +142,29 @@ def indent_html(rawcode):
# if a opening raw tag then start ignoring.. only if there is no closing tag
# on the same line
if (
re.search("|".join(ignored_group_opening), item, re.IGNORECASE)
) or re.search("|".join(ignored_block_opening), item, re.IGNORECASE):
re.search(config.ignored_group_opening, item, re.IGNORECASE | re.VERBOSE)
) or re.search(config.ignored_block_opening, item, re.IGNORECASE | re.VERBOSE):
is_block_raw = True
# if a normal tag, we can try to expand attributes
elif (
format_long_attributes
config.format_long_attributes
and is_block_raw is False
and len(tmp) > max_line_length
and len(tmp) > int(config.max_line_length)
):
# get leading space, and attributes
tmp = re.sub(r"(\s*?)(<\w+\s)([^>]+?)(/?>)", _format_attributes, tmp)
func = partial(_format_attributes, config)
tmp = re.sub(r"(\s*?)(<\w+\s)([^>]+?)(/?>)", func, tmp)
# turn off raw block if we hit end - for one line raw blocks
if (
re.search("|".join(ignored_group_closing), item, re.IGNORECASE)
) or re.search("|".join(ignored_block_closing), item, re.IGNORECASE):
re.search(
re.sub(r"\s", "", config.ignored_group_closing), item, re.IGNORECASE
)
) or re.search(
re.sub(r"\s", "", config.ignored_block_closing), item, re.IGNORECASE
):
is_block_raw = False
beautified_code = beautified_code + tmp

View file

@ -1,9 +1,12 @@
"""Djlint html linter."""
from pathlib import Path
from typing import Dict, List
import regex as re
import yaml
from .settings import Config
rules = yaml.load(
(Path(__file__).parent / "rules.yaml").read_text(encoding="utf8"),
Loader=yaml.SafeLoader,
@ -24,7 +27,7 @@ flags = {
}
def build_flags(flag_list):
def build_flags(flag_list: str) -> int:
"""Build list of regex flags."""
split_flags = flag_list.split("|")
@ -34,14 +37,14 @@ def build_flags(flag_list):
return combined_flags
def get_line(start, line_ends):
def get_line(start: int, line_ends: List) -> str:
"""Get the line number and index of match."""
line = list(filter(lambda pair: pair["end"] > start, line_ends))[0]
return "%d:%d" % (line_ends.index(line) + 1, start - line["start"])
def lint_file(ignore: str, this_file: Path):
def lint_file(config: Config, this_file: Path) -> Dict:
"""Check file for formatting errors."""
file_name = str(this_file)
errors: dict = {file_name: []}
@ -54,7 +57,7 @@ def lint_file(ignore: str, this_file: Path):
]
for rule in list(
filter(lambda x: x["rule"]["name"] not in ignore.split(","), rules)
filter(lambda x: x["rule"]["name"] not in config.ignore.split(","), rules)
):
rule = rule["rule"]

View file

@ -9,9 +9,10 @@ from pathlib import Path
from .formatter.compress_html import compress_html
from .formatter.expand_html import expand_html
from .formatter.indent_html import indent_html
from .settings import Config
def reformat_file(check: bool, this_file: Path):
def reformat_file(config: Config, check: bool, this_file: Path) -> dict:
"""Reformat html file."""
rawcode = this_file.read_text(encoding="utf8")
@ -21,9 +22,9 @@ def reformat_file(check: bool, this_file: Path):
while itteration < 10:
expanded = expand_html(rawcode)
compressed = compress_html(expanded)
indented = indent_html(compressed)
expanded = expand_html(rawcode, config)
compressed = compress_html(expanded, config)
indented = indent_html(compressed, config)
if (
len(

View file

@ -116,6 +116,7 @@
flags: re.DOTALL|re.I
patterns:
- <(img|input|area|base|br|col|embed|hr|link|meta|param|source|track|wbr|command|keygen|menuitem|path)[^>]*?[^/]>
- <(img|input|area|base|br|col|embed|hr|link|meta|param|source|track|wbr|command|keygen|menuitem|path)>
- rule:
name: W018
message: Internal links should use the {% url ... %} pattern.

View file

@ -2,234 +2,521 @@
# pylint: disable=C0301,C0103
# flake8: noqa
# default indentation
indent = " "
# contents of tags will not be formatted, but tags will be formatted
ignored_block_opening = [r"<style" r"{\*", r"<\?php", r"<script"]
import logging
ignored_block_closing = [r"</style" r"\*}", r"\?>", r"</script"]
## get pyproject.toml settings
from pathlib import Path
from typing import Dict, Optional, Union
import tomlkit
logger = logging.getLogger(__name__)
# contents of tags will not be formated and tags will not be formatted
ignored_group_opening = [r"<!--", r"[^\{]{#", r"<pre", r"<textarea"]
def find_pyproject(src: Path) -> Optional[Path]:
"""Search upstream for a pyprojec.toml file."""
ignored_group_closing = [r"-->", r"#}", r"</pre", r"</textarea"]
for directory in [src, *src.resolve().parents]:
candidate = directory / "pyproject.toml"
if candidate.is_file():
return candidate
return None
# the contents of these tag blocks will be indented, then unindented
tag_indent = r"(?:\{\{\#)|\{% +?(if|for|block|else|spaceless|compress|addto|language|with|assets)|(?:{% verbatim %})|(?:<(?:html|head|body|div|a|nav|ul|ol|dl|li|table|thead|tbody|tr|th|td|blockquote|select|form|option|cache|optgroup|fieldset|legend|label|header|main|section|aside|footer|figure|video|span|p|g|svg|h\d|button|img|path|script|style|source))"
tag_unindent = r"(?:\{\{\/)|\{% end|(?:{% endverbatim %})|(?:</(?:html|head|body|div|a|nav|ul|ol|dl|li|table|thead|tbody|tr|th|td|blockquote|select|form|option|optgroup|fieldset|legend|label|header|cache|main|section|aside|footer|figure|video|span|p|g|svg|h\d|button|img|path|script|style|source))"
def load_pyproject_settings(src: Path) -> Dict:
"""Load djlint config from pyproject.toml."""
# these tags should be unindented and next line will be indented
tag_unindent_line = r"(?:\{% el)|(?:\{\{ *?(?:else|\^) *?\}\})"
djlint_content = {}
pyproject_file = find_pyproject(src)
if pyproject_file:
content = tomlkit.parse(pyproject_file.read_text())
try:
djlint_content = content["tool"]["djlint"]
except KeyError:
logger.info("No pyproject.toml found.")
return djlint_content
# reduce empty lines greater than x to 1 line
reduce_extralines_gt = 2
def build_custom_blocks(custom_blocks: Union[str, None]) -> Optional[str]:
"""Build regex string for custom template blocks."""
if custom_blocks:
return "|" + "|".join(x.strip() for x in custom_blocks.split(","))
return None
# if lines are longer than x
max_line_length = 120
format_long_attributes = True
# pattern used to find attributes in a tag
attribute_pattern = r"(?:{%[^}]*?%}(?:.*?{%[^}]*?%})+?)|(?:[^\s]+?=(?:\"{{.*?}}\"|\'{{.*?}}\'))|(?:[^\s]+?=(?:\".*?\"|\'.*?\'))|required|checked|[\w|-]+|[\w|-]+=[\w|-]+|{{.*?}}"
tag_pattern = r"(<\w+?[^>]*?)((?:\n[^>]+?)+?)(/?\>)"
ignored_attributes = [
"data-json",
]
class Config:
"""Djling Config."""
ignored_paths = [
".venv",
"venv",
".tox",
".eggs",
".git",
".hg",
".mypy_cache",
".nox",
".svn",
".bzr",
"_build",
"buck-out",
"build",
"dist",
".pants.d",
".direnv",
"node_modules",
"__pypackages__",
]
def __init__(
self,
src: str,
ignore: Optional[str] = None,
extension: Optional[str] = None,
quiet: Optional[bool] = False,
):
start_template_tags = r"{% ?(?:if|for|block|spaceless|compress|load|assets|addto|language|with|assets)[^}]+?%}"
self.ignored_paths: str = r"""
\.venv
| venv
| \.tox
| \.eggs
| \.git
| \.hg
| \.mypy_cache
| \.nox
| \.svn
| \.bzr
| _build
| buck-out
| build
| dist
| \.pants\.d
| \.direnv
| node_modules
| __pypackages__
"""
break_template_tags = [
r"{% ?(?:if|end|for|block|endblock|else|spaceless|compress|load|include|assets|addto|language|with|assets)[^}]+?%}",
]
djlint_settings = load_pyproject_settings(Path(src))
unformated_html_tags = ["script"]
# custom configuration options
self.extension: str = str(extension or djlint_settings.get("extension", "html"))
self.ignore: str = str(ignore or djlint_settings.get("ignore", ""))
self.quiet: str = str(quiet or djlint_settings.get("quiet", ""))
self.custom_blocks: str = str(
build_custom_blocks(djlint_settings.get("custom_blocks")) or ""
)
ignored_blocks = [
r"<(script|style|pre|textarea).*?(?:%s).*?</(\1)>",
r"<!--.*?(?:%s).*?-->",
r"{\*.*?(?:%s).*?\*}",
r"{#.*?(?:%s).*?#}",
r"<\?php.*?(?:%s).*?\?>",
]
# base options
self.indent: str = djlint_settings.get("indent", " ")
ignored_inline_blocks = [
r"<!--.*?-->",
r"{\*.*?\*}",
r"{#.*?#}",
r"<\?php.*?\?>",
]
# contents of tags will not be formatted, but tags will be formatted
self.ignored_block_opening: str = r"""
<style
| {\*
| <\?php
| <script
"""
single_line_html_tags = [
"button",
"a",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"td",
"th",
"strong",
"em",
"icon",
"span",
"title",
"link",
"path",
"label",
"div",
]
self.ignored_block_closing: str = r"""
</style
| \*}
| \?>
| </script
"""
always_single_line_html_tags = ["link", "img", "meta"]
# contents of tags will not be formated and tags will not be formatted
self.ignored_group_opening: str = r"""
<!--
| [^\{]{\#
| <pre
| <textarea
"""
single_line_template_tags = ["if", "for", "block", "with"]
self.ignored_group_closing: str = r"""
-->
| \#}
| </pre
| </textarea
"""
break_html_tags = [
"a",
"abbr",
"acronym",
"address",
"applet",
"area",
"article",
"aside",
"audio",
"b",
"base",
"basefont",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"center",
"cite",
"code",
"col",
"colgroup",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"dir",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"font",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"icon",
"img",
"input",
"ins",
"kbd",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"meta",
"meter",
"nav",
"noframes",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"path",
"param",
"picture",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strike",
"strong",
"style",
"sub",
"summary",
"sup",
"svg",
"table",
"tbody",
"td",
"template",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"tt",
"u",
"ul",
"var",
"video",
"wbr",
]
# the contents of these tag blocks will be indented, then unindented
self.tag_indent: str = (
r"""
(?:\{\{\#|\{%)[ ]*?
(
if
| for
| block
| else
| spaceless
| compress
| addto
| language
| with
| assets
| verbatim
| autoescape
| comment
| filter
| each
"""
+ self.custom_blocks
+ r"""
)
| (?:<
(?:
html
| head
| body
| div
| a
| nav
| ul
| ol
| dl
| li
| table
| thead
| tbody
| tr
| th
| td
| blockquote
| select
| form
| option
| cache
| optgroup
| fieldset
| legend
| label
| header
| main
| section
| aside
| footer
| figure
| video
| span
| p
| g
| svg
| h\d
| button
| img
| path
| script
| style
| source
)
)
"""
)
self.tag_unindent: str = r"""^
(?:
(?:\{\{\/)
| (?:\{%[ ]*?end)
)
| (?:</
(?:
html
| head
| body
| div
| a
| nav
| ul
| ol
| dl
| li
| table
| thead
| tbody
| tr
| th
| td
| blockquote
| select
| form
| option
| optgroup
| fieldset
| legend
| label
| header
| cache
| main
| section
| aside
| footer
| figure
| video
| span
| p
| g
| svg
| h\d
| button
| img
| path
| script
| style
| source
)
)
"""
# these tags should be unindented and next line will be indented
self.tag_unindent_line: str = r"""
(?:\{%[ ]*?(?:elif|else|empty))
| (?:
\{\{[ ]*?
(
(?:else|\^)
[ ]*?\}\}
)
)
"""
# reduce empty lines greater than x to 1 line
self.reduce_extralines_gt = 2
# if lines are longer than x
self.max_line_length = 120
self.format_long_attributes = True
# pattern used to find attributes in a tag
self.attribute_pattern: str = r"""
(?:{%[^}]*?%}(?:.*?{%[^}]*?%})+?)
| (?:[^\s]+?=(?:\"{{.*?}}\"|\'{{.*?}}\'))
| (?:[^\s]+?=(?:\".*?\"|\'.*?\'))
| required
| checked
| [\w|-]+
| [\w|-]+=[\w|-]+
| {{.*?}}
"""
self.tag_pattern: str = r"""
(<\w+?[^>]*?)((?:\n[^>]+?)+?)(/?\>)
"""
self.ignored_attributes: list = [r"""data-json"""]
self.start_template_tags: str = (
r"""
if
| for
| block
| spaceless
| compress
| load
| assets
| addto
| language
| with
| assets
| autoescape
| comment
| filter
| verbatim
| each
"""
+ self.custom_blocks
+ r"""
"""
)
self.break_template_tags: str = (
r"""
if
| end
| for
| block
| endblock
| else
| spaceless
| compress
| load
| include
| assets
| addto
| language
| with
| assets
| autoescape
| comment
| filter
| elif
| resetcycle
| verbatim
| each
"""
+ self.custom_blocks
+ r"""
"""
)
self.ignored_blocks: list = [
r"<(script|style|pre|textarea).*?(?:%s).*?</(\1)>",
r"<!--.*?(?:%s).*?-->",
r"{\*.*?(?:%s).*?\*}",
r"{\#.*?(?:%s).*?#}",
r"<\?php.*?(?:%s).*?\?>",
]
self.ignored_inline_blocks: str = r"""
<!--.*?-->
| {\*.*?\*}
| {\#.*?\#}
| <\?php.*?\?>
"""
self.single_line_html_tags: str = r"""
button
| a
| h1
| h2
| h3
| h4
| h5
| h6
| td
| th
| strong
| em
| icon
| span
| title
| link
| path
| label
| div
| li
"""
self.always_single_line_html_tags: str = r"""
link
| img
| meta
"""
self.single_line_template_tags: str = r"""
if
| for
| block
| with
| comment
"""
self.break_html_tags: str = r"""
a
| abbr
| acronym
| address
| applet
| area
| article
| aside
| audio
| b
| base
| basefont
| bdi
| bdo
| big
| blockquote
| body
| br
| button
| canvas
| caption
| center
| cite
| code
| col
| colgroup
| data
| datalist
| dd
| del
| details
| dfn
| dialog
| dir
| div
| dl
| dt
| em
| embed
| fieldset
| figcaption
| figure
| font
| footer
| form
| frame
| frameset
| h1
| h2
| h3
| h4
| h5
| h6
| head
| header
| hr
| html
| i
| iframe
| icon
| img
| input
| ins
| kbd
| label
| legend
| li
| link
| main
| map
| mark
| meta
| meter
| nav
| noframes
| noscript
| object
| ol
| optgroup
| option
| output
| p
| path
| param
| picture
| progress
| q
| rp
| rt
| ruby
| s
| samp
| script
| section
| select
| small
| source
| span
| strike
| strong
| style
| sub
| summary
| sup
| svg
| table
| tbody
| td
| template
| tfoot
| th
| thead
| time
| title
| tr
| track
| tt
| u
| ul
| var
| video
| wbr
"""