diff --git a/src/djlint/__init__.py b/src/djlint/__init__.py index ad77789..67b57aa 100644 --- a/src/djlint/__init__.py +++ b/src/djlint/__init__.py @@ -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 } diff --git a/src/djlint/formatter/compress_html.py b/src/djlint/formatter/compress_html.py index dceef14..55af006 100644 --- a/src/djlint/formatter/compress_html.py +++ b/src/djlint/formatter/compress_html.py @@ -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*?()" % 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*?()" % 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*?()" % slt_html, + r"(<(%s)[ ][^\n]{,80}>)\s*([^<\n]{,80})\s*?()" % 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, diff --git a/src/djlint/formatter/expand_html.py b/src/djlint/formatter/expand_html.py index 5b5dd39..49db712 100644 --- a/src/djlint/formatter/expand_html.py +++ b/src/djlint/formatter/expand_html.py @@ -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
+ # the tag either opens
# self closes # has attributes
# or has attributes and self closes @@ -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"(?)" @@ -88,62 +85,71 @@ 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, ) # \n and \n 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, ) # and 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, \n, \n 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, + 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"()(?=[^\n])" % html_tags, add_right, html, flags=re.IGNORECASE + r"()(?=[^\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 diff --git a/src/djlint/formatter/indent_html.py b/src/djlint/formatter/indent_html.py index 8197cdb..58bfc58 100644 --- a/src/djlint/formatter/indent_html.py +++ b/src/djlint/formatter/indent_html.py @@ -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)>)(.*?)()" % slt_html, item, re.IGNORECASE) - or re.findall(r"(<(%s) .+?>)(.*?)()" % slt_html, item, re.IGNORECASE) - or re.findall( - r"^({% +?(" + slt_template + r") +?.+?%})(.*?)({% +?end(\2) +?.*?%})", - item, - re.IGNORECASE | re.MULTILINE, + re.findall( + r"(<(%s)>)(.*?)()" % 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)[ ].+?>)(.*?)()" % 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 diff --git a/src/djlint/lint.py b/src/djlint/lint.py index c9818bf..b473602 100644 --- a/src/djlint/lint.py +++ b/src/djlint/lint.py @@ -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"] diff --git a/src/djlint/reformat.py b/src/djlint/reformat.py index b11dad0..96fa941 100644 --- a/src/djlint/reformat.py +++ b/src/djlint/reformat.py @@ -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( diff --git a/src/djlint/rules.yaml b/src/djlint/rules.yaml index 64e6869..c554789 100644 --- a/src/djlint/rules.yaml +++ b/src/djlint/rules.yaml @@ -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. diff --git a/src/djlint/settings.py b/src/djlint/settings.py index 67620ec..4b07f8d 100644 --- a/src/djlint/settings.py +++ b/src/djlint/settings.py @@ -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"", r" Optional[Path]: + """Search upstream for a pyprojec.toml file.""" -ignored_group_closing = [r"-->", r"#}", r" 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).*?", - r"", - 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""" +