djLint/src/djlint/settings.py

745 lines
21 KiB
Python
Raw Normal View History

2021-07-23 20:58:24 +00:00
"""Settings for reformater."""
2021-07-26 14:05:55 +00:00
# pylint: disable=C0301,C0103
2021-07-23 20:58:24 +00:00
# flake8: noqa
2021-07-26 14:05:55 +00:00
2022-02-18 20:15:36 +00:00
import json
import logging
## get pyproject.toml settings
from pathlib import Path
2021-09-30 08:02:05 +00:00
from typing import Dict, List, Optional, Union
2021-10-04 13:26:22 +00:00
import yaml
from click import echo
from colorama import Fore
from HtmlTagNames import html_tag_names
from HtmlVoidElements import html_void_elements
2021-10-29 08:42:39 +00:00
from pathspec import PathSpec
from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
try:
import tomllib
except ImportError:
import tomli as tomllib # type: ignore
logger = logging.getLogger(__name__)
2021-10-29 08:42:39 +00:00
def find_project_root(src: Path) -> Path:
"""Attempt to get the project root."""
for directory in [src, *src.resolve().parents]:
if (directory / ".git").exists():
return directory
if (directory / ".hg").is_dir():
return directory
if (directory / "pyproject.toml").is_file():
return directory
2022-02-18 20:15:36 +00:00
if (directory / ".djlintrc").is_file():
return directory
2021-10-29 08:42:39 +00:00
# pylint: disable=W0631
return directory
def load_gitignore(root: Path) -> PathSpec:
2021-10-29 11:49:30 +00:00
"""Search upstream for a .gitignore file."""
2021-10-29 08:42:39 +00:00
gitignore = root / ".gitignore"
git_lines: List[str] = []
if gitignore.is_file():
with gitignore.open(encoding="utf-8") as this_file:
git_lines = this_file.readlines()
try:
return PathSpec.from_lines("gitwildmatch", git_lines)
2021-10-29 08:42:39 +00:00
except GitWildMatchPatternError as e:
echo(f"Could not parse {gitignore}: {e}", err=True)
raise
2021-10-29 08:42:39 +00:00
def find_pyproject(root: Path) -> Optional[Path]:
2021-10-29 11:49:30 +00:00
"""Search upstream for a pyproject.toml file."""
2021-10-29 08:42:39 +00:00
pyproject = root / "pyproject.toml"
if pyproject.is_file():
return pyproject
return None
2022-02-18 20:15:36 +00:00
def find_djlintrc(root: Path) -> Optional[Path]:
"""Search upstream for a pyproject.toml file."""
djlintrc = root / ".djlintrc"
if djlintrc.is_file():
return djlintrc
return None
2021-10-29 08:42:39 +00:00
def find_djlint_rules(root: Path) -> Optional[Path]:
2021-10-04 13:26:22 +00:00
"""Search upstream for a pyprojec.toml file."""
2021-10-29 08:42:39 +00:00
rules = root / ".djlint_rules.yaml"
2021-10-04 13:26:22 +00:00
2021-10-29 08:42:39 +00:00
if rules.is_file():
return rules
2021-10-04 13:26:22 +00:00
return None
2022-02-18 20:15:36 +00:00
def load_project_settings(src: Path) -> Dict:
"""Load djlint config from pyproject.toml."""
2021-09-08 09:52:16 +00:00
djlint_content: Dict = {}
pyproject_file = find_pyproject(src)
if pyproject_file:
content = tomllib.load(pyproject_file.open("rb"))
try:
2022-02-18 20:15:36 +00:00
return content["tool"]["djlint"] # type: ignore
except KeyError:
logger.info("No pyproject.toml found.")
2022-02-18 20:15:36 +00:00
djlintrc_file = find_djlintrc(src)
if djlintrc_file:
try:
return json.loads(djlintrc_file.read_text(encoding="utf8"))
# pylint: disable=broad-except
except BaseException:
logger.info("Failed to load .djlintrc file.")
return djlint_content
2021-10-04 13:26:22 +00:00
def validate_rules(rules: List) -> List:
2021-10-04 13:29:07 +00:00
"""Validate a list of linter rules. Returns valid rules."""
2021-10-04 13:26:22 +00:00
clean_rules = []
for rule in rules:
# check for name
warning = 0
name = rule["rule"].get("name", "undefined")
if "name" not in rule["rule"]:
warning += 1
2021-10-04 13:29:07 +00:00
echo(Fore.RED + "Warning: A rule is missing a name! 😢")
2021-10-04 13:26:22 +00:00
if "patterns" not in rule["rule"]:
warning += 1
echo(Fore.RED + f"Warning: Rule {name} is missing a pattern! 😢")
if "message" not in rule["rule"]:
warning += 1
echo(Fore.RED + f"Warning: Rule {name} is missing a message! 😢")
if warning == 0:
clean_rules.append(rule)
return clean_rules
def load_custom_rules(src: Path) -> List:
"""Load djlint config from pyproject.toml."""
djlint_content: List = []
djlint_rules_file = find_djlint_rules(src)
if djlint_rules_file:
djlint_content = yaml.load(
Path(djlint_rules_file).read_text(encoding="utf8"),
Loader=yaml.SafeLoader,
)
return djlint_content
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
2021-12-30 19:54:59 +00:00
def build_custom_html(custom_html: Union[str, None]) -> Optional[str]:
"""Build regex string for custom HTML blocks."""
if custom_html:
return "|" + "|".join(x.strip() for x in custom_html.split(","))
return None
2021-12-30 19:54:59 +00:00
class Config:
2021-10-29 11:49:30 +00:00
"""Djlint Config."""
def __init__(
self,
src: str,
ignore: Optional[str] = None,
extension: Optional[str] = None,
2021-09-28 11:42:13 +00:00
indent: Optional[int] = None,
2021-10-14 10:19:48 +00:00
quiet: bool = False,
2021-09-30 08:02:05 +00:00
profile: Optional[str] = None,
2021-10-14 10:19:48 +00:00
require_pragma: bool = False,
reformat: bool = False,
check: bool = False,
lint: bool = False,
2021-10-29 08:42:39 +00:00
use_gitignore: bool = False,
2022-03-14 14:24:28 +00:00
warn: bool = False,
preserve_leading_space: bool = False,
preserve_blank_lines: bool = False,
format_css: bool = False,
format_js: bool = False,
):
2021-10-14 10:19:48 +00:00
self.reformat = reformat
self.check = check
self.lint = lint
2022-03-14 14:24:28 +00:00
self.warn = warn
2021-10-14 10:19:48 +00:00
self.stdin = "-" in src
2021-11-26 13:01:54 +00:00
self.project_root = find_project_root(Path(src))
2021-10-29 08:42:39 +00:00
2022-02-18 20:15:36 +00:00
djlint_settings = load_project_settings(self.project_root)
2021-11-26 13:01:54 +00:00
self.gitignore = load_gitignore(self.project_root)
# custom configuration options
2021-10-29 08:42:39 +00:00
self.use_gitignore: bool = use_gitignore or djlint_settings.get(
"use_gitignore", False
)
self.extension: str = str(extension or djlint_settings.get("extension", "html"))
2021-10-14 10:19:48 +00:00
self.quiet: bool = quiet or djlint_settings.get("quiet", False)
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 ""
)
2021-12-30 19:54:59 +00:00
self.custom_html: str = str(
build_custom_html(djlint_settings.get("custom_html")) or ""
)
self.format_attribute_template_tags: bool = djlint_settings.get(
2021-11-29 11:19:07 +00:00
"format_attribute_template_tags", False
)
self.preserve_leading_space: bool = (
preserve_leading_space
or djlint_settings.get("preserve_leading_space", False)
2021-11-29 11:19:07 +00:00
)
self.preserve_blank_lines: bool = preserve_blank_lines or djlint_settings.get(
"preserve_blank_lines", False
)
self.format_js: bool = format_js or djlint_settings.get("format_js", False)
self.js_config = djlint_settings.get("js")
self.css_config = djlint_settings.get("css")
self.format_css: bool = format_css or djlint_settings.get("format_css", False)
2021-09-30 08:02:05 +00:00
# ignore is based on input and also profile
self.ignore: str = str(ignore or djlint_settings.get("ignore", ""))
# codes to exclude
profile_dict: Dict[str, List[str]] = {
2021-12-14 08:57:37 +00:00
"html": ["D", "J", "T", "N", "M"],
2021-09-30 08:02:05 +00:00
"django": ["J", "N", "M"],
"jinja": ["D", "N", "M"],
"nunjucks": ["D", "J", "M"],
"handlebars": ["D", "J", "N"],
2021-10-04 14:31:09 +00:00
"golang": ["D", "J", "N", "M"],
2021-12-11 13:26:19 +00:00
"angular": ["D", "J", "H012", "H026", "H028"],
2021-09-30 08:02:05 +00:00
}
self.profile_code: List[str] = profile_dict.get(
2022-02-18 19:07:30 +00:00
str(profile or djlint_settings.get("profile", "html")).lower(), []
2021-09-30 08:02:05 +00:00
)
self.profile: str = str(
profile or djlint_settings.get("profile", "all")
).lower()
self.linter_output_format: str = djlint_settings.get(
"linter_output_format", "{code} {line} {message} {match}"
)
2021-10-04 13:26:22 +00:00
# load linter rules
rule_set = validate_rules(
yaml.load(
(Path(__file__).parent / "rules.yaml").read_text(encoding="utf8"),
Loader=yaml.SafeLoader,
)
2021-11-26 13:01:54 +00:00
+ load_custom_rules(self.project_root)
2021-10-04 13:26:22 +00:00
)
self.linter_rules = list(
filter(
lambda x: x["rule"]["name"] not in self.ignore.split(",")
2021-12-11 13:26:19 +00:00
and not any(
x["rule"]["name"].startswith(code) for code in self.profile_code
)
2021-10-04 13:26:22 +00:00
and self.profile not in x["rule"].get("exclude", []),
rule_set,
)
)
# base options
2021-10-08 10:56:12 +00:00
default_indent = 4
if not indent:
try:
indent = int(djlint_settings.get("indent", default_indent))
except ValueError:
echo(
Fore.RED
+ f"Error: Invalid pyproject.toml indent value {djlint_settings['indent']}"
)
indent = default_indent
self.indent: str = indent * " "
default_exclude: str = r"""
\.venv
2021-10-29 08:42:39 +00:00
| venv/
| \.tox
| \.eggs
| \.git
| \.hg
| \.mypy_cache
| \.nox
| \.svn
| \.bzr
2021-10-29 08:42:39 +00:00
| _build/
| buck-out/
| build/
| dist/
| \.pants\.d
| \.direnv
2021-10-29 08:42:39 +00:00
| node_modules/
| __pypackages__
"""
self.exclude: str = djlint_settings.get("exclude", default_exclude)
2021-09-21 11:05:49 +00:00
extend_exclude: str = djlint_settings.get("extend_exclude", "")
2021-09-21 11:05:16 +00:00
if extend_exclude:
self.exclude += r" | " + r" | ".join(
2021-10-28 11:39:38 +00:00
x.strip() for x in extend_exclude.split(",")
)
self.per_file_ignores = djlint_settings.get("per-file-ignores", {})
2021-09-23 08:19:11 +00:00
# add blank line after load tags
self.blank_line_after_tag: Optional[str] = djlint_settings.get(
"blank_line_after_tag", None
)
# add blank line before load tags
self.blank_line_before_tag: Optional[str] = djlint_settings.get(
"blank_line_before_tag", None
)
# contents of tags will not be formatted
self.ignored_block_opening: str = r"""
<style
| {\*
| <\?php
| <script
| <!--
| [^\{]{\#(?!\s*djlint\:\s*(?:on|off))
| ^{\#(?!\s*djlint\:\s*(?:on|off))
| <pre
| <textarea
2022-01-07 14:49:00 +00:00
| {%[ ]*?blocktrans(?:late)?[^(?:%})]*?%}
2022-01-03 15:43:55 +00:00
| {\#\s*djlint\:\s*off\s*\#}
| {%[ ]+?comment[ ]+?[^(?:%})]*?%}
2022-01-03 15:43:55 +00:00
| {{!--\s*djlint\:off\s*--}}
| {{-?\s*/\*\s*djlint\:off\s*\*/\s*-?}}
"""
self.ignored_block_closing: str = r"""
</style
| \*}
| \?>
| </script
| -->
2022-07-18 12:47:23 +00:00
| ^(?:(?!{\#).)*\#} # lines that have a #}, but not a {#
| </pre
| </textarea
2022-01-03 15:43:55 +00:00
| {\#\s*djlint\:\s*on\s*\#}
| {%[ ]+?endcomment[ ]+?%}
2022-01-03 15:43:55 +00:00
| {{!--\s*djlint\:on\s*--}}
| {{-?\s*/\*\s*djlint\:on\s*\*/\s*-?}}
2022-01-07 14:49:00 +00:00
| {%[ ]*?endblocktrans(?:late)?[^(?:%})]*?%}
"""
2021-10-14 11:53:47 +00:00
# ignored block closing tags that
# we can safely indent.
2021-10-14 11:53:47 +00:00
self.safe_closing_tag: str = r"""
</script
| </style
2022-01-07 14:49:00 +00:00
| {\#\s*djlint\:\s*on\s*\#}
| {%[ ]+?endcomment[ ]+?%}
| {{!--\s*djlint\:on\s*--}}
| {{-?\s*/\*\s*djlint\:on\s*\*/\s*-?}}
2021-10-14 11:53:47 +00:00
"""
2021-10-06 12:20:47 +00:00
# all html tags possible
self.indent_html_tags: str = "|".join(html_tag_names) + self.custom_html
2021-10-06 12:20:47 +00:00
2021-10-06 13:27:18 +00:00
self.indent_template_tags: str = (
r""" if
| for
2022-01-06 19:41:15 +00:00
| block(?!trans|translate)
# | blocktrans(?:late)?[ ]+?trimmed
2021-10-06 13:27:18 +00:00
| spaceless
| compress
| addto
| language
| with
| assets
| verbatim
| autoescape
| filter
| each
2021-10-28 10:42:51 +00:00
| macro
2022-01-06 19:41:15 +00:00
| raw
2021-10-06 13:27:18 +00:00
"""
+ self.custom_blocks
)
2021-10-29 06:54:20 +00:00
self.template_indent: str = (
r"""
2021-10-29 06:54:20 +00:00
(?:\{\{\#|\{%-?)[ ]*?
2021-10-06 13:27:18 +00:00
("""
+ self.indent_template_tags
+ r"""
2021-10-29 06:54:20 +00:00
)"""
)
self.template_unindent: str = r"""
(?:
(?:\{\{\/)
2022-01-24 18:03:44 +00:00
| (?:\{%-?[ ]*?end(?!comment))
2021-10-29 06:54:20 +00:00
)
"""
# these tags should be unindented and next line will be indented
self.tag_unindent_line: str = r"""
2021-09-08 09:52:16 +00:00
(?:\{%-?[ ]*?(?:elif|else|empty))
| (?:
\{\{[ ]*?
(
(?:else|\^)
[ ]*?\}\}
)
)
"""
self.break_before = r"(?<!\n[ ]*?)"
# if lines are longer than x
self.max_line_length = 120
2021-10-14 10:19:48 +00:00
2021-10-08 11:05:00 +00:00
try:
self.max_line_length = int(
djlint_settings.get("max_line_length", self.max_line_length)
)
except ValueError:
echo(
Fore.RED
+ f"Error: Invalid pyproject.toml max_line_length value {djlint_settings['max_line_length']}"
)
2021-10-14 10:19:48 +00:00
self.max_attribute_length = 70
try:
self.max_attribute_length = int(
djlint_settings.get("max_attribute_length", self.max_attribute_length)
)
except ValueError:
echo(
Fore.RED
+ f"Error: Invalid pyproject.toml max_attribute_length value {djlint_settings['max_attribute_length']}"
)
# pattern used to find attributes in a tag
# order is important.
# 1. attributes="{% if %}with if or for statement{% endif %}"
# 2. attributes="{{ stuff in here }}"
# 3. {% if %}with if or for statement{% endif %}
# 4. attributes="normal html"
# 5. require | checked | otherword | other-word
# 6. {{ stuff }}
self.template_if_for_pattern = (
r"(?:{%-?\s?(?:if|for)[^}]*?%}(?:.*?{%\s?end(?:if|for)[^}]*?-?%})+?)"
)
self.attribute_pattern: str = (
r"""
(?:[^\s]+?=(?:\"[^\"]*?"""
+ self.template_if_for_pattern
+ r"""[^\"]*?\"|\'[^\']*?"""
+ self.template_if_for_pattern
+ r"""[^\']*?\'))
| (?:[^\s]+?=(?:\"[^\"]*?{{.*?}}[^\"]*?\"|\'[^\']*?{{.*?}}[^\']*?\'))
| """
+ self.template_if_for_pattern
+ r"""
2021-10-06 07:19:37 +00:00
| (?:[^\s]+?=(?:\"(?:[^\"]*?{%[^}]*?%}[^\"]*?)+?\"))
| (?:[^\s]+?=(?:\'(?:[^\']*?{%[^}]*?%}[^\']*?)+?\'))
| (?:[^\s]+?=(?:\".*?\"|\'.*?\'))
| required
| checked
| (?:\w|-|\.)+
| (?:\w|-|\.)+=(?:\w|-)+
| {{.*?}}
2021-10-29 07:02:48 +00:00
| {%.*?%}
"""
)
2021-10-29 06:54:20 +00:00
self.attribute_style_pattern: str = r"^(.*?)(style=)([\"|'])(([^\"']+?;)+?)\3"
self.start_template_tags: str = (
r"""
if
| for
2022-01-06 19:41:15 +00:00
| block(?!trans)
| spaceless
| compress
| load
| assets
| addto
| language
| with
| assets
| autoescape
| filter
| verbatim
| each
2021-10-28 10:42:51 +00:00
| macro
2022-01-06 19:41:15 +00:00
| raw
"""
+ self.custom_blocks
+ r"""
"""
)
self.break_template_tags: str = (
r"""
if
2022-01-06 19:41:15 +00:00
| endif
| for
2022-01-06 19:41:15 +00:00
| endfor
| block(?!trans)
| endblock(?!trans)
| else
| spaceless
2022-01-06 19:41:15 +00:00
| endspaceless
| compress
2022-01-06 19:41:15 +00:00
| endcompress
| load
| include
| assets
2022-01-06 19:41:15 +00:00
| endassets
| addto
| language
| with
2022-01-06 19:41:15 +00:00
| endwith
| autoescape
2022-01-06 19:41:15 +00:00
| endautoescape
| filter
2022-01-06 19:41:15 +00:00
| endfilter
| elif
| resetcycle
| verbatim
2022-01-06 19:41:15 +00:00
| endverbatim
| each
2021-10-28 10:42:51 +00:00
| macro
2022-01-06 19:41:15 +00:00
| endmacro
| raw
| endraw
"""
+ self.custom_blocks
+ r"""
"""
)
2021-10-01 09:08:52 +00:00
self.ignored_blocks: str = r"""
2021-10-14 11:53:47 +00:00
<(pre|textarea).*?</(\1)>
| <(script|style).*?(?=(\</(?:\3)>))
2021-10-14 10:19:48 +00:00
# html comment
2022-01-07 14:49:00 +00:00
| <!--\s*djlint\:off\s*-->.*?(?=<!--\s*djlint\:on\s*-->)
2022-01-03 15:43:55 +00:00
# django/jinja/nunjucks
2022-01-07 14:49:00 +00:00
| {\#\s*djlint\:\s*off\s*\#}.*?(?={\#\s*djlint\:\s*on\s*\#})
| {%\s*comment\s*%\}\s*djlint\:off\s*\{%\s*endcomment\s*%\}.*?(?={%\s*comment\s*%\}\s*djlint\:on\s*\{%\s*endcomment\s*%\})
# inline jinja comments
| {\#(?!\s*djlint\:\s*(?:off|on)).*?\#}
2021-10-14 10:19:48 +00:00
# handlebars
2022-01-07 14:49:00 +00:00
| {{!--\s*djlint\:off\s*--}}.*?(?={{!--\s*djlint\:on\s*--}})
2021-10-14 10:19:48 +00:00
# golang
2022-01-07 14:49:00 +00:00
| {{-?\s*/\*\s*djlint\:off\s*\*/\s*-?}}.*?(?={{-?\s*/\*\s*djlint\:on\s*\*/\s*-?}})
2021-10-01 09:08:52 +00:00
| <!--.*?-->
| <\?php.*?\?>
2022-01-07 14:49:00 +00:00
| {%[ ]*?blocktranslate\b[^(?:%})]*?%}.*?{%[ ]*?endblocktranslate[ ]*?%}
| {%[ ]*?blocktrans\b[^(?:%})]*?%}.*?{%[ ]*?endblocktrans[ ]*?%}
| {%[ ]*?comment\b[^(?:%})]*?%}.*?(?={%[ ]*?endcomment[ ]*?%})
2022-02-18 17:16:55 +00:00
| ^---[\s\S]+?---
2021-10-01 09:08:52 +00:00
"""
self.ignored_rules: List[str] = [
# html comment
r"<!--\s*djlint\:off(.+?)-->.*?(?=<!--\s*djlint\:on\s*-->)",
# django/jinja/nunjucks
r"{\#\s*djlint\:\s*off(.+?)\#}.*?(?={\#\s*djlint\:\s*on\s*\#})",
r"{%\s*comment\s*%\}\s*djlint\:off(.*?)\{%\s*endcomment\s*%\}.*?(?={%\s*comment\s*%\}\s*djlint\:on\s*\{%\s*endcomment\s*%\})",
# handlebars
r"{{!--\s*djlint\:off(.*?)--}}.*?(?={{!--\s*djlint\:on\s*--}})",
# golang
r"{{-?\s*/\*\s*djlint\:off(.*?)\*/\s*-?}}.*?(?={{-?\s*/\*\s*djlint\:on\s*\*/\s*-?}})",
]
self.ignored_inline_blocks: str = r"""
<!--.*?-->
2022-01-24 18:03:44 +00:00
| <(script|style).*?\</(?:\1)>
| {\*.*?\*}
2022-01-07 14:49:00 +00:00
| {\#(?!.*djlint:[ ]*?(?:off|on)\b).*\#}
| <\?php.*?\?>
2021-11-24 08:18:58 +00:00
| {%[ ]*?comment\b[^(?:%})]*?%}.*?{%[ ]*?endcomment[ ]*?%}
2022-01-07 14:49:00 +00:00
| {%[ ]*?blocktranslate\b[^(?:%})]*?%}.*?{%[ ]*?endblocktranslate[ ]*?%}
| {%[ ]*?blocktrans\b[^(?:%})]*?%}.*?{%[ ]*?endblocktrans[ ]*?%}
"""
self.optional_single_line_html_tags: str = r"""
button
| a
| h1
| h2
| h3
| h4
| h5
| h6
| td
| th
| strong
2021-09-16 07:48:41 +00:00
| small
| em
| icon
| span
| title
| link
| path
| label
| div
| li
| script
| style
| head
| body
| p
"""
self.always_self_closing_html_tags: str = "|".join(html_void_elements)
self.optional_single_line_template_tags: str = r"""
if
| for
| block
| with
"""
2021-10-11 08:44:51 +00:00
self.break_html_tags: str = (
r"""
2021-10-06 12:20:47 +00:00
html
| head
| body
| div
2022-01-31 22:16:17 +00:00
# | a # a gets no breaks #177
2021-10-06 12:20:47 +00:00
| nav
| ul
| ol
| dl
2021-10-06 12:20:47 +00:00
| dd
| dt
2021-10-06 12:20:47 +00:00
| li
| table
| thead
| tbody
| tr
| th
| td
| blockquote
| select
| form
2021-10-06 12:20:47 +00:00
| option
| optgroup
| fieldset
| legend
2021-10-06 12:20:47 +00:00
| label
| header
| cache
| main
2021-10-06 12:20:47 +00:00
| section
| aside
| footer
| figure
| figcaption
| video
2022-01-14 15:12:19 +00:00
# | span # span gets no breaks #171
| p
2021-10-06 12:20:47 +00:00
| g
| svg
| h\d
| button
| path
| picture
| script
| style
2021-10-06 12:20:47 +00:00
| details
| summary
2021-10-11 08:44:51 +00:00
| """
+ self.always_self_closing_html_tags
2022-01-03 14:45:38 +00:00
+ self.custom_html
2021-10-11 08:44:51 +00:00
+ """
"""
2021-10-11 08:44:51 +00:00
)
# the contents of these tag blocks will be indented, then unindented
self.tag_indent: str = (
self.template_indent
+ """
| (?:<
(?:
"""
+ self.indent_html_tags
+ """
)\\b
)
"""
)
self.tag_unindent: str = (
r"""
^
"""
+ self.template_unindent
+ """
| (?:</
(?:
"""
+ self.indent_html_tags
+ """
)\\b
)
"""
)