diff --git a/docs/src/_data/configuration.json b/docs/src/_data/configuration.json index 81d0695..302e52c 100644 --- a/docs/src/_data/configuration.json +++ b/docs/src/_data/configuration.json @@ -143,6 +143,24 @@ } ] }, + { + "name": "blank_line_before_tag", + "description": { + "en": "Add an additional blank line before `{% ... %}` tag groups. Blank lines will never be added to start of file or between similar tags.", + "ru": "Добавляет дополнительную пустую строку перед группами тегов `{% ... %}`. Пустые строки никогда не будут добавлены в начало файла или между похожими тегами.", + "fr": "Ajoute une ligne blanche supplémentaire avant les groupes de balises `{% ... %}`. Les lignes vides ne seront jamais ajoutées au début du fichier ou entre des balises similaires." + }, + "usage": [ + { + "name": "pyproject.toml", + "value": "blank_line_before_tag=\"load,extends,include\"" + }, + { + "name": ".djlintrc", + "value": "\"blank_line_before_tag\": \"load,extends,include\"" + } + ] + }, { "name": "profile", "description": { diff --git a/src/djlint/formatter/condense.py b/src/djlint/formatter/condense.py index ebd165a..f4d4ec4 100644 --- a/src/djlint/formatter/condense.py +++ b/src/djlint/formatter/condense.py @@ -70,24 +70,43 @@ def condense_html(html: str, config: Config) -> str: ) return True + def if_blank_line_before_match(config: Config, html: str) -> bool: + """Check if there should be a blank line before.""" + if config.blank_line_before_tag: + return not any( + re.findall( + re.compile( + rf"((?:{{%\s*?{tag}[^}}]+?%}}\n?)+)", + re.IGNORECASE | re.MULTILINE | re.DOTALL, + ), + html, + ) + for tag in [x.strip() for x in config.blank_line_before_tag.split(",")] + ) + return True + def condense_line(config: Config, match: re.Match) -> str: """Put contents on a single line if below max line length.""" if ( - len(match.group(1) + match.group(3) + match.group(4)) - < config.max_line_length - ) and if_blank_line_after_match(config, match.group(3)): + ( + len(match.group(1) + match.group(3) + match.group(4)) + < config.max_line_length + ) + and if_blank_line_after_match(config, match.group(3)) + and if_blank_line_before_match(config, match.group(3)) + ): return match.group(1) + match.group(3) + match.group(4) return match.group() - def add_blank_line(config: Config, html: str, match: re.Match) -> str: + def add_blank_line_after(config: Config, html: str, match: re.Match) -> str: """Add break after if not in ignored block.""" if inside_ignored_block(config, html, match): return match.group() return match.group() + "\n" - func = partial(add_blank_line, config, html) + func = partial(add_blank_line_after, config, html) # should we add blank lines after load tags? if config.blank_line_after_tag: @@ -101,6 +120,27 @@ def condense_html(html: str, config: Config) -> str: html, ) + def add_blank_line_before(config: Config, html: str, match: re.Match) -> str: + """Add break before if not in ignored block and not first line in file.""" + if inside_ignored_block(config, html, match) or match.start() == 0: + return match.group() + + return "\n" + match.group() + + func = partial(add_blank_line_before, config, html) + + # should we add blank lines before load tags? + if config.blank_line_before_tag: + for tag in [x.strip() for x in config.blank_line_before_tag.split(",")]: + html = re.sub( + re.compile( + rf"(? diff --git a/tests/test_config/test_blank_lines_before_tag/html_eight.html b/tests/test_config/test_blank_lines_before_tag/html_eight.html new file mode 100644 index 0000000..fe01ca0 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_eight.html @@ -0,0 +1,3 @@ +{% extends nothing %} + +
diff --git a/tests/test_config/test_blank_lines_before_tag/html_five.html b/tests/test_config/test_blank_lines_before_tag/html_five.html new file mode 100644 index 0000000..ae38524 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_five.html @@ -0,0 +1,6 @@ + + + + {% include "common/pdfs/partials/display_quality_improvements.html" %} + + diff --git a/tests/test_config/test_blank_lines_before_tag/html_four.html b/tests/test_config/test_blank_lines_before_tag/html_four.html new file mode 100644 index 0000000..f115c2f --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_four.html @@ -0,0 +1,3 @@ +{% block this %} +{% load i18n %} +{% endblock this %} diff --git a/tests/test_config/test_blank_lines_before_tag/html_seven.html b/tests/test_config/test_blank_lines_before_tag/html_seven.html new file mode 100644 index 0000000..41352a2 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_seven.html @@ -0,0 +1,6 @@ +{% blocktrans %}my words{% endblocktrans %} +{% block body %} +
+ +{% endblock body %} +{% block js %}{% endblock %} diff --git a/tests/test_config/test_blank_lines_before_tag/html_six.html b/tests/test_config/test_blank_lines_before_tag/html_six.html new file mode 100644 index 0000000..ede15b2 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_six.html @@ -0,0 +1,4 @@ +{% block include %} + {# {% include 'common/sticky-topbar-hidden-nav.html' %}#} + +{% endblock %} diff --git a/tests/test_config/test_blank_lines_before_tag/html_three.html b/tests/test_config/test_blank_lines_before_tag/html_three.html new file mode 100644 index 0000000..fac2077 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_three.html @@ -0,0 +1,8 @@ +
+
+
+ + {% include "pages/task/details_source.html.j2" %} +
+
+
diff --git a/tests/test_config/test_blank_lines_before_tag/html_two.html b/tests/test_config/test_blank_lines_before_tag/html_two.html new file mode 100644 index 0000000..f887fcb --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/html_two.html @@ -0,0 +1,2 @@ +{% load stuff %} +
diff --git a/tests/test_config/test_blank_lines_before_tag/pyproject.toml b/tests/test_config/test_blank_lines_before_tag/pyproject.toml new file mode 100644 index 0000000..fec3ea8 --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/pyproject.toml @@ -0,0 +1,3 @@ +[tool] +[tool.djlint] +blank_line_before_tag = "load, extends, include ,endblock " diff --git a/tests/test_config/test_blank_lines_before_tag/test_config.py b/tests/test_config/test_blank_lines_before_tag/test_config.py new file mode 100644 index 0000000..c252eea --- /dev/null +++ b/tests/test_config/test_blank_lines_before_tag/test_config.py @@ -0,0 +1,101 @@ +"""Djlint tests specific to pyproject.toml configuration. + +run:: + + pytest tests/test_config/test_blank_lines_before_tag/test_config.py --cov=src/djlint --cov-branch \ + --cov-report xml:coverage.xml --cov-report term-missing + +for a single test, run:: + + pytest tests/test_config.py::test_custom_html --cov=src/djlint \ + --cov-branch --cov-report xml:coverage.xml --cov-report term-missing + +""" +# pylint: disable=C0116 + +from click.testing import CliRunner + +from src.djlint import main as djlint + + +def test_blank_lines_before_tag(runner: CliRunner) -> None: + result = runner.invoke( + djlint, ["tests/test_config/test_blank_lines_before_tag/html.html", "--check"] + ) + + assert ( + """+{% extends "nothing.html" %} ++ ++{% load stuff %} ++{% load stuff 2 %} ++ ++{% include "html_two.html" %} ++
""" + in result.output + ) + assert """1 file would be updated.""" in result.output + assert result.exit_code == 1 + + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_two.html", "--check"], + ) + assert result.exit_code == 0 + + # check blocks that do not start on a newline - they should be left as is. + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_three.html", "--check"], + ) + + assert """0 files would be updated.""" in result.output + assert result.exit_code == 0 + + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_four.html", "--check"], + ) + + assert result.exit_code == 1 + assert ( + """ {% block this %} +-{% load i18n %} ++ ++ {% load i18n %} ++ + {% endblock this %} +""" + in result.output + ) + + # something perfect should stay perfect :) + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_five.html", "--check"], + ) + assert result.exit_code == 0 + + # something perfect should stay perfect :) + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_six.html", "--check"], + ) + assert result.exit_code == 0 + + # make sure endblock doesn't pick up endblocktrans :) + result = runner.invoke( + djlint, + ["tests/test_config/test_blank_lines_before_tag/html_seven.html", "--check"], + ) + assert result.exit_code == 0 + + # check that multiple blank lines are not added + result = runner.invoke( + djlint, + [ + "tests/test_config/test_blank_lines_before_tag/html_eight.html", + "--preserve-blank-lines", + "--check", + ], + ) + assert result.exit_code == 0