djLint/src/djlint/settings.py

630 lines
15 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
import logging
import re
## get pyproject.toml settings
from pathlib import Path
2021-09-30 08:02:05 +00:00
from typing import Dict, List, Optional, Union
import tomlkit
2021-10-04 13:26:22 +00:00
import yaml
from click import echo
from colorama import Fore
logger = logging.getLogger(__name__)
def find_pyproject(src: Path) -> Optional[Path]:
"""Search upstream for a pyprojec.toml file."""
for directory in [src, *src.resolve().parents]:
candidate = directory / "pyproject.toml"
if candidate.is_file():
return candidate
return None
2021-10-04 13:26:22 +00:00
def find_djlint_rules(src: Path) -> Optional[Path]:
"""Search upstream for a pyprojec.toml file."""
for directory in [src, *src.resolve().parents]:
candidate = directory / ".djlint_rules.yaml"
if candidate.is_file():
return candidate
return None
def load_pyproject_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:
2021-10-07 13:35:19 +00:00
content = tomlkit.parse(pyproject_file.read_text(encoding="utf8"))
try:
2021-09-08 09:52:16 +00:00
djlint_content = content["tool"]["djlint"] # type: ignore
except KeyError:
logger.info("No pyproject.toml found.")
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
class Config:
"""Djling 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,
quiet: Optional[bool] = False,
2021-09-30 08:02:05 +00:00
profile: Optional[str] = None,
):
djlint_settings = load_pyproject_settings(Path(src))
# 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.custom_blocks: str = str(
build_custom_blocks(djlint_settings.get("custom_blocks")) or ""
)
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]] = {
"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-09-30 08:02:05 +00:00
}
self.profile_code: List[str] = profile_dict.get(
str(profile or djlint_settings.get("profile", "all")).lower(), []
)
self.profile: str = str(
profile or djlint_settings.get("profile", "all")
).lower()
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,
)
+ load_custom_rules(Path(src))
)
self.linter_rules = list(
filter(
lambda x: x["rule"]["name"] not in self.ignore.split(",")
and x["rule"]["name"][0] not in self.profile_code
and self.profile not in x["rule"].get("exclude", []),
rule_set,
)
)
# base options
2021-09-28 11:42:13 +00:00
self.indent: str = (indent or int(djlint_settings.get("indent", 4))) * " "
default_exclude: str = r"""
\.venv
| venv
| \.tox
| \.eggs
| \.git
| \.hg
| \.mypy_cache
| \.nox
| \.svn
| \.bzr
| _build
| buck-out
| build
| dist
| \.pants\.d
| \.direnv
| 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(
re.escape(x.strip()) for x in extend_exclude.split(",")
)
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
)
# contents of tags will not be formatted
self.ignored_block_opening: str = r"""
<style
| {\*
| <\?php
| <script
| <!--
| [^\{]{\#
| <pre
| <textarea
| {%[ ]djlint:off[ ]%}
| {%[ ]+?comment[ ]+?[^(?:%})]*?%}
"""
self.ignored_block_closing: str = r"""
</style
| \*}
| \?>
| </script
| -->
| \#}
| </pre
| </textarea
| {%[ ]djlint:on[ ]%}
| {%[ ]+?endcomment[ ]+?%}
"""
2021-10-06 12:20:47 +00:00
# all html tags possible
self.indent_html_tags: str = r"""
a
| abbr
| acronym
| address
| applet
| area
| article
| aside
| audio
| b
| base
| basefont
| bdi
| bdo
| big
| blockquote
| body
| br
| button
| canvas
| caption
| center
| cite
| code
| col
| colgroup
| data
| datalist
| dd
| del
| details
| dfn
| dialog
| dir
| div
| dl
| dt
| em
| embed
| fieldset
| figcaption
| figure
| font
| footer
| form
| frame
| frameset
| h1
| h2
| h3
| h4
| h5
| h6
| head
| header
| hr
| html
| i
| iframe
| icon
| img
| input
| ins
| kbd
| label
| legend
| li
| link
| main
| map
| mark
| meta
| meter
| nav
| noframes
| noscript
| object
| ol
| optgroup
| option
| output
| p
| path
| param
| picture
| progress
| q
| rp
| rt
| ruby
| s
| samp
| script
| section
| select
| small
| source
| span
| strike
| strong
| style
| sub
| summary
| sup
| svg
| table
| tbody
| td
| template
| tfoot
| th
| thead
| time
| title
| tr
| track
| tt
| u
| ul
| var
| video
| wbr
"""
2021-10-06 13:27:18 +00:00
self.indent_template_tags: str = (
r""" if
| for
| block
| else
| spaceless
| compress
| addto
| language
| with
| assets
| verbatim
| autoescape
| filter
| each
"""
+ self.custom_blocks
)
# the contents of these tag blocks will be indented, then unindented
self.tag_indent: str = (
r"""
2021-09-08 09:52:16 +00:00
(?:\{\{\#|\{%-?)[ ]*?
2021-10-06 13:27:18 +00:00
("""
+ self.indent_template_tags
+ r"""
)
| (?:<
(?:
2021-10-06 12:20:47 +00:00
"""
+ self.indent_html_tags
+ """
)
)
"""
)
2021-10-06 12:20:47 +00:00
self.tag_unindent: str = (
r"""^
(?:
(?:\{\{\/)
2021-09-08 09:52:16 +00:00
| (?:\{%-?[ ]*?end)
)
| (?:</
(?:
2021-10-06 12:20:47 +00:00
"""
+ self.indent_html_tags
+ """
)
)
"""
2021-10-06 12:20:47 +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
self.format_long_attributes = True
# 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|-]+
| {{.*?}}
"""
)
self.attribute_style_pattern: str = r"(.*?)(style=)([\"|'])(([^\"']+?;)+?)\3"
self.start_template_tags: str = (
r"""
if
| for
| block
| spaceless
| compress
| load
| assets
| addto
| language
| with
| assets
| autoescape
| filter
| verbatim
| each
"""
+ self.custom_blocks
+ r"""
"""
)
self.break_template_tags: str = (
r"""
if
| end
| for
| block
| endblock
| else
| spaceless
| compress
| load
| include
| assets
| addto
| language
| with
| assets
| autoescape
| filter
| elif
| resetcycle
| verbatim
| each
"""
+ self.custom_blocks
+ r"""
"""
)
2021-10-01 09:08:52 +00:00
self.ignored_blocks: str = r"""
<(script|style|pre|textarea).*?</(\1)>
| {%[ ]djlint:off[ ]%}.*?{%[ ]djlint:on[ ]%}
2021-10-01 09:08:52 +00:00
| <!--.*?-->
| {\*.*?\*}
| {\#.*?\#}
| <\?php.*?\?>
2021-10-06 06:54:43 +00:00
| {\%[ ]trans[ ][^}]*?\%}
| {%[ ]+?comment[ ]+?[^(?:%})]*?%}.*?{%[ ]+?endcomment[ ]+?%}
2021-10-01 09:08:52 +00:00
"""
self.ignored_inline_blocks: str = r"""
<!--.*?-->
| {\*.*?\*}
| {\#.*?\#}
| <\?php.*?\?>
| {%[ ]+?comment[ ]+?[^(?:%})]*?%}.*?{%[ ]+?endcomment[ ]+?%}
"""
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
"""
self.always_self_closing_html_tags: str = r"""
link
| img
| meta
2021-09-30 12:28:40 +00:00
| source
| br
| input
"""
self.optional_single_line_template_tags: str = r"""
if
| for
| block
| with
"""
self.break_html_tags: str = r"""
2021-10-06 12:20:47 +00:00
html
| head
| body
| div
2021-10-06 12:20:47 +00:00
| a
| 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
| span
| p
2021-10-06 12:20:47 +00:00
| g
| svg
| h\d
| button
| img
| path
| picture
| script
| style
2021-10-06 12:20:47 +00:00
| details
| summary
"""