diff --git a/docs/djlint/configuration.rst b/docs/djlint/configuration.rst index 4c2fca2..fd7e94f 100644 --- a/docs/djlint/configuration.rst +++ b/docs/djlint/configuration.rst @@ -104,3 +104,26 @@ Usage: .. code:: ini profile="django" + +require_pragma +-------------- + +Only format files that starts with a comment with only the word 'format'. The comment can be a HTML comment or a comment in the template language defined by the profile setting. If no profile is specified, a comment in any of the template languages is accepted. + +Usage: + +.. code:: ini + + require_pragma = true + +.. code:: html + + + or + {# format #} + or + {% comment %} format {% endcomment %} + or + {{ /* format */ }} + or + {{!-- format --}} diff --git a/src/djlint/__init__.py b/src/djlint/__init__.py index 46580c2..c7948d0 100644 --- a/src/djlint/__init__.py +++ b/src/djlint/__init__.py @@ -200,6 +200,11 @@ def build_quantity_tense(size: int) -> str: type=str, help="Enable defaults by template language. ops: django, jinja, nunjucks, handlebars, golang", ) +@click.option( + "--require-pragma", + is_flag=True, + help="Only formats files that starts with a comment with only the word 'format'", +) def main( src: List[str], extension: str, @@ -209,6 +214,7 @@ def main( check: bool, quiet: bool, profile: str, + require_pragma: str, ) -> None: """djLint ยท lint and reformat HTML templates.""" config = Config( @@ -218,6 +224,7 @@ def main( indent=indent, quiet=quiet, profile=profile, + require_pragma=require_pragma, ) temp_file = None diff --git a/src/djlint/reformat.py b/src/djlint/reformat.py index 0c1adef..3970edf 100644 --- a/src/djlint/reformat.py +++ b/src/djlint/reformat.py @@ -5,17 +5,56 @@ Much code is borrowed from https://github.com/rareyman/HTMLBeautify, many thanks import difflib from pathlib import Path +import regex as re 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 +html_patterns = [re.compile(r"")] +django_jinja_patterns = [ + re.compile(r"\{#\s*format\s*#\}"), + re.compile(r"\{%\s*comment\s*%\}\s*format\s*\{%\s*endcomment\s*%\}"), +] +nunjucks_patterns = [re.compile(r"\{#\s*format\s*#\}")] +handlebars_patterns = [re.compile(r"\{\{!--\s*format\s*--\}\}")] +golang_patterns = [ + re.compile(r"\{\{\s*/\*\s*format\s*\*/\s*\}\}"), + re.compile(r"\{\{-\s*/\*\s*format\s*/\*\s*-\}\}"), +] + + +def has_pragma(config: Config, html_str: str): + first_line = html_str.partition("\n")[0] + + pragma_patterns = { + "django": django_jinja_patterns + html_patterns, + "jinja": django_jinja_patterns + html_patterns, + "nunjucks": nunjucks_patterns + html_patterns, + "handlebars": handlebars_patterns + html_patterns, + "golang": golang_patterns + html_patterns, + "all": django_jinja_patterns + + nunjucks_patterns + + handlebars_patterns + + golang_patterns + + html_patterns, + } + + for pattern in pragma_patterns[config.profile]: + if re.match(pattern, first_line): + return True + return False + def reformat_file(config: Config, check: bool, this_file: Path) -> dict: """Reformat html file.""" rawcode = this_file.read_text(encoding="utf8") + if config.require_pragma and not has_pragma(config, rawcode): + # The file should not be reformatted + return {this_file: []} + expanded = expand_html(rawcode, config) compressed = compress_html(expanded, config) indented = indent_html(compressed, config) diff --git a/src/djlint/settings.py b/src/djlint/settings.py index 9a0955a..c496d8c 100644 --- a/src/djlint/settings.py +++ b/src/djlint/settings.py @@ -117,6 +117,7 @@ class Config: indent: Optional[int] = None, quiet: Optional[bool] = False, profile: Optional[str] = None, + require_pragma: Optional[bool] = False, ): djlint_settings = load_pyproject_settings(Path(src)) @@ -124,6 +125,10 @@ class Config: # custom configuration options self.extension: str = str(extension or djlint_settings.get("extension", "html")) self.quiet: str = str(quiet or djlint_settings.get("quiet", "")) + self.require_pragma: bool = ( + require_pragma + or str(djlint_settings.get("require_pragma", "false")).lower() == "true" + ) self.custom_blocks: str = str( build_custom_blocks(djlint_settings.get("custom_blocks")) or "" ) diff --git a/tests/config_pragmas/html_five.html b/tests/config_pragmas/html_five.html new file mode 100644 index 0000000..c27f06f --- /dev/null +++ b/tests/config_pragmas/html_five.html @@ -0,0 +1,2 @@ + +{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %}
diff --git a/tests/config_pragmas/html_four.html b/tests/config_pragmas/html_four.html new file mode 100644 index 0000000..b376898 --- /dev/null +++ b/tests/config_pragmas/html_four.html @@ -0,0 +1,5 @@ +{{ /* format */ }} +{{ .Variable }}
+{{ range .Items }}{{ . }} + +
{{ end }} diff --git a/tests/config_pragmas/html_one.html b/tests/config_pragmas/html_one.html new file mode 100644 index 0000000..0cf231e --- /dev/null +++ b/tests/config_pragmas/html_one.html @@ -0,0 +1 @@ +{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} diff --git a/tests/config_pragmas/html_six.html b/tests/config_pragmas/html_six.html new file mode 100644 index 0000000..8a76b61 --- /dev/null +++ b/tests/config_pragmas/html_six.html @@ -0,0 +1,2 @@ +{% comment %} format {% endcomment %} +{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} diff --git a/tests/config_pragmas/html_three.html b/tests/config_pragmas/html_three.html new file mode 100644 index 0000000..34736ba --- /dev/null +++ b/tests/config_pragmas/html_three.html @@ -0,0 +1,4 @@ +{{!-- format --}} ++ +{{firstname}}
{{lastname}}
diff --git a/tests/config_pragmas/html_two.html b/tests/config_pragmas/html_two.html new file mode 100644 index 0000000..f9834b0 --- /dev/null +++ b/tests/config_pragmas/html_two.html @@ -0,0 +1,2 @@ +{# format #} +{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} diff --git a/tests/config_pragmas/pyproject.toml b/tests/config_pragmas/pyproject.toml new file mode 100644 index 0000000..8cc97e6 --- /dev/null +++ b/tests/config_pragmas/pyproject.toml @@ -0,0 +1,3 @@ +[tool] +[tool.djlint] +require_pragma=true diff --git a/tests/test_config.py b/tests/test_config.py index 430355f..bdb39a4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -183,3 +183,105 @@ def test_profile(runner: CliRunner) -> None: +{{ test }}""" in result.output ) + + +def test_require_pragma(runner: CliRunner) -> None: + result = runner.invoke( + djlint, ["tests/config_pragmas/html_one.html", "--check", "--profile", "django"] + ) + + assert """0 files would be updated.""" in result.output + assert result.exit_code == 0 + + result = runner.invoke( + djlint, ["tests/config_pragmas/html_two.html", "--check", "--profile", "django"] + ) + assert ( + """ {# format #} +-{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} ++{% 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/config_pragmas/html_three.html", "--check", "--profile", "handlebars"], + ) + + assert ( + """ {{!-- format --}} ++- +-{{firstname}}
{{lastname}}
++ {{firstname}} ++ ++++ {{lastname}} ++
""" + in result.output + ) + assert """1 file would be updated.""" in result.output + assert result.exit_code == 1 + + result = runner.invoke( + djlint, + ["tests/config_pragmas/html_four.html", "--check", "--profile", "golang"], + ) + + assert ( + """ {{ /* format */ }} +-{{ .Variable }}
+-{{ range .Items }}{{ . }} +- +-
{{ end }} ++++ {{ .Variable }} ++
++{{ range .Items }} ++++ {{ . }} ++
++{{ end }} + +1 file would be updated.""" + in result.output + ) + assert """1 file would be updated.""" in result.output + assert result.exit_code == 1 + + result = runner.invoke(djlint, ["tests/config_pragmas/html_five.html", "--check"]) + assert ( + """ +-{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} ++{% 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/config_pragmas/html_six.html", "--check", "--profile", "django"] + ) + assert ( + """ {% comment %} format {% endcomment %} +-{% extends "nothing.html" %}{% load stuff %}{% load stuff 2 %}{% include "html_two.html" %} ++{% 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