diff --git a/docs/src/_data/configuration.json b/docs/src/_data/configuration.json index d9d6bd6a9..ac4e6843d 100644 --- a/docs/src/_data/configuration.json +++ b/docs/src/_data/configuration.json @@ -1,4 +1,27 @@ [ + { + "name": "max_blank_lines", + "description": { + "en": "Consolidate blank lines down to x lines. Default is 0 meaning blank lines will be removed.", + "ru": "Объедините пустые строки до х строк. По умолчанию 0, что означает, что пустые строки будут удалены.", + "fr": "Consolider les lignes vierges en les ramenant à x lignes. La valeur par défaut est 0, ce qui signifie que les lignes vierges seront supprimées." + }, + "usage": [ + { + "name": "pyproject.toml", + "value": "max_blank_lines=5" + }, + { + "name": ".djlintrc", + "value": "\"max_blank_lines\": 5" + }, + { + "name": "cli", + "value": "--max_blank_lines 5" + } + ], + "tags": ["formatter"] + }, { "name": "no_set_formatting", "description": { diff --git a/docs/src/_includes/cli.md b/docs/src/_includes/cli.md index d00b53b0d..22053cc0b 100644 --- a/docs/src/_includes/cli.md +++ b/docs/src/_includes/cli.md @@ -57,6 +57,7 @@ Options: matter. --no-function-formatting Do not attempt to format function contents. --no-set-formatting Do not attempt to format set contents. + --max-blank-lines INTEGER Consolidate blank lines down to x lines. [default: 0] -h, --help Show this message and exit. ``` diff --git a/docs/src/docs/linter.md b/docs/src/docs/linter.md index 6c54a19fd..e8e41d034 100644 --- a/docs/src/docs/linter.md +++ b/docs/src/docs/linter.md @@ -67,7 +67,7 @@ This can also be done through the [{{ "configuration" | i18n }}]({{ "lang_code_u | H033 | Extra whitespace found in form action. | ✔️ | | J004 | (Jinja) Static urls should follow {% raw %}`{{ url_for('static'..) }}`{% endraw %} pattern. | ✔️ | | J018 | (Jinja) Internal links should use the {% raw %}`{% url ... %}`{% endraw %} pattern. | ✔️ | -| T001 | Variables should be wrapped in a single whitespace. Ex: {% raw %}`{{ this }}`{% endraw %} | ✔️ | +| T001 | Variables should be wrapped in whitespace. Ex: {% raw %}`{{ this }}`{% endraw %} | ✔️ | | T002 | Double quotes should be used in tags. Ex {% raw %}`{% extends "this.html" %}`{% endraw %} | ✔️ | | T003 | Endblock should have name. Ex: {% raw %}`{% endblock body %}`{% endraw %}. | ✔️ | | T027 | Unclosed string found in template syntax. | ✔️ | diff --git a/docs/src/fr/docs/linter.md b/docs/src/fr/docs/linter.md index 0713885a6..0635f54de 100644 --- a/docs/src/fr/docs/linter.md +++ b/docs/src/fr/docs/linter.md @@ -67,7 +67,7 @@ Cela peut également se faire par l'intermédiaire de l'option [{{ "configuratio | H033 | Espace supplémentaire dans l'action du formulaire. | ✔️ | | J004 | (Jinja) Les urls statiques doivent suivre le modèle {% raw %}`{ url_for('static'..) }}`{% endraw %}. | ✔️ | | J018 | (Jinja) Les liens internes doivent utiliser le modèle {% raw %}`{% url ... %}`{% endraw %}. | ✔️ | -| T001 | Les variables doivent être entourées d'un seul espace. Ex : {% raw %}`{{ this }}`{% endraw %} | ✔️ | +| T001 | Les variables doivent être entourées d'un espace. Ex : {% raw %}`{{ this }}`{% endraw %} | ✔️ | | T002 | Les doubles quotes doivent être utilisées dans les balises. Ex : {% raw %}`{% extends "this.html" %}`{% endraw %} | ✔️ | | T003 | Le bloc de fin doit avoir un nom. Ex : {% raw %}`{% endblock body %}`{% endraw %}. | ✔️ | | T027 | Chaîne non fermée trouvée dans la syntaxe du modèle. | ✔️ | diff --git a/docs/src/ru/docs/linter.md b/docs/src/ru/docs/linter.md index 1d619e6b7..bf95ad9a6 100644 --- a/docs/src/ru/docs/linter.md +++ b/docs/src/ru/docs/linter.md @@ -67,7 +67,7 @@ djlint . --lint --include=H017,H035 --ignore=H013,H015 | H033 | В действии формы обнаружен лишний пробел. | ✔️ | | J004 | (Jinja) Статические урлы должны следовать шаблону {% raw %}`{{ url_for('static'...)}}`{% endraw %}. | ✔️ | | J018 | (Jinja) Внутренние ссылки должны использовать шаблон {% raw %}`{% url ... %}`{% endraw %}. | ✔️ | -| T001 | Переменные должны быть заключены в один пробел. Например: {% raw %}`{{ this }}`{% endraw %} | ✔️ | +| T001 | Переменные должны быть заключены в пробел. Например: {% raw %}`{{ this }}`{% endraw %} | ✔️ | | T002 | В тегах следует использовать двойные кавычки. Ex {% raw %}`{% extends "this.html" %}`{% endraw %} | ✔️ | | T003 | Конечный блок должен иметь имя. Например: {% raw %}`{% endblock body %}`{% endraw %}. | ✔️ | | T027 | В синтаксисе шаблона найдена незакрытая строка. | ✔️ | diff --git a/src/djlint/__init__.py b/src/djlint/__init__.py index ae4b2b5b9..5461e454b 100644 --- a/src/djlint/__init__.py +++ b/src/djlint/__init__.py @@ -247,6 +247,12 @@ is_flag=True, help="Do not attempt to format set contents.", ) +@click.option( + "--max-blank-lines", + type=int, + help="Consolidate blank lines down to x lines. [default: 0]", + show_default=False, +) @colorama_text(autoreset=True) def main( src: List[str], @@ -288,6 +294,7 @@ def main( no_line_after_yaml: bool, no_function_formatting: bool, no_set_formatting: bool, + max_blank_lines: Optional[int], ) -> None: """djLint · HTML template linter and formatter.""" config = Config( @@ -330,6 +337,7 @@ def main( no_line_after_yaml=no_line_after_yaml, no_function_formatting=no_function_formatting, no_set_formatting=no_set_formatting, + max_blank_lines=max_blank_lines, ) temp_file = None diff --git a/src/djlint/formatter/condense.py b/src/djlint/formatter/condense.py index a2af9e8f9..6a66b61f8 100644 --- a/src/djlint/formatter/condense.py +++ b/src/djlint/formatter/condense.py @@ -36,7 +36,16 @@ def strip_space(config: Config, html: str, match: re.Match) -> str: if inside_protected_trans_block(config, html[: match.end()], match): return match.group().rstrip() - return match.group(1) + lines = len( + re.findall( + r"\n", + match.group(2), + ) + ) + blank_lines = "\n" * lines + if lines > config.max_blank_lines: + blank_lines = "\n" * max(config.max_blank_lines, 0) + return match.group(1) + blank_lines func = partial(strip_space, config, html) @@ -50,7 +59,7 @@ def strip_space(config: Config, html: str, match: re.Match) -> str: if not config.preserve_leading_space: # remove any leading/trailing space html = re.sub( - re.compile(rf"^[ \t]*{line_contents}[{trailing_contents}]*$", re.M), + re.compile(rf"^[ \t]*{line_contents}([{trailing_contents}]*)$", re.M), func, html, ) @@ -59,12 +68,12 @@ def strip_space(config: Config, html: str, match: re.Match) -> str: # only remove leading space in front of tags # <, {% html = re.sub( - re.compile(rf"^[ \t]*((?:<|{{%).*?)[{trailing_contents}]*$", re.M), + re.compile(rf"^[ \t]*((?:<|{{%).*?)([{trailing_contents}]*)$", re.M), func, html, ) html = re.sub( - re.compile(rf"^{line_contents}[{trailing_contents}]*$", re.M), func, html + re.compile(rf"^{line_contents}([{trailing_contents}]*)$", re.M), func, html ) def add_blank_line_after(config: Config, html: str, match: re.Match) -> str: diff --git a/src/djlint/settings.py b/src/djlint/settings.py index 8ad1f948a..282990930 100644 --- a/src/djlint/settings.py +++ b/src/djlint/settings.py @@ -254,6 +254,7 @@ def __init__( no_line_after_yaml: bool = False, no_function_formatting: bool = False, no_set_formatting: bool = False, + max_blank_lines: Optional[int] = None, ): self.reformat = reformat self.check = check @@ -405,6 +406,17 @@ def __init__( self.indent_size = indent self.indent: str = int(indent) * " " + try: + self.max_blank_lines = int( + djlint_settings.get("max_blank_lines", max_blank_lines or 0) + ) + except ValueError: + echo( + Fore.RED + + f"Error: Invalid pyproject.toml indent value {djlint_settings['max_blank_lines']}" + ) + self.max_blank_lines = max_blank_lines or 0 + default_exclude: str = r""" \.venv | venv/ diff --git a/tests/test_config/test_max_blank_lines.py b/tests/test_config/test_max_blank_lines.py new file mode 100644 index 000000000..211a2fe0d --- /dev/null +++ b/tests/test_config/test_max_blank_lines.py @@ -0,0 +1,55 @@ +"""Test for max blank lines. + +poetry run pytest tests/test_config/test_max_blank_lines.py +""" +import pytest + +from src.djlint.reformat import formatter +from tests.conftest import config_builder, printer + +test_data = [ + pytest.param( + ("\n\n\n\n\n\n\n\n\n\n"), + ("\n" "\n"), + ({}), + id="default", + ), + pytest.param( + ("\n\n\n\n\n\n\n\n\n\n"), + ("\n\n\n\n\n\n" "\n"), + ({"max_blank_lines": 5}), + id="5", + ), + pytest.param( + ("\n\n\n\n\n\n\n\n\n\n"), + ("\n\n" "\n"), + ({"max_blank_lines": 1}), + id="1", + ), + pytest.param( + ("\n\n\n\n\n\n\n\n\n\n"), + ("\n" "\n"), + ({"max_blank_lines": -1}), + id="-1", + ), + pytest.param( + ("\n\n\n\n\n\n\n\n\n\n"), + ("\n\n\n\n\n\n\n\n\n\n" "\n"), + ({"max_blank_lines": 30}), + id="30", + ), + pytest.param( + ("\n\n
\n\n
\n\n