djLint/src/djlint/__init__.py

316 lines
7.9 KiB
Python
Raw Normal View History

2021-07-12 18:26:46 +00:00
#!/usr/bin/python
2021-07-13 17:25:29 +00:00
"""
Check Django template syntax.
2021-07-12 18:26:46 +00:00
usage::
2021-07-29 18:41:34 +00:00
djlint src
2021-07-12 18:26:46 +00:00
2021-07-23 20:58:24 +00:00
options:
2021-07-29 18:41:34 +00:00
-e or --extension | <extension>
2021-07-23 20:58:24 +00:00
--check | will check html formatting for needed changes
--reformat | will reformat html
2021-07-12 18:26:46 +00:00
"""
2021-07-13 17:25:29 +00:00
2021-07-12 18:26:46 +00:00
import os
2021-07-26 14:05:55 +00:00
import re
2021-08-17 13:56:40 +00:00
import shutil
2021-07-12 18:26:46 +00:00
import sys
from concurrent.futures import ProcessPoolExecutor, as_completed
2021-07-23 20:58:24 +00:00
from functools import partial
2021-07-12 18:26:46 +00:00
from pathlib import Path
import click
from click import echo
from colorama import Fore, Style, deinit, init
from tqdm import tqdm
2021-07-12 18:26:46 +00:00
2021-07-29 18:41:34 +00:00
from .lint import lint_file
from .reformat import reformat_file
from .settings import ignored_paths
2021-07-12 18:26:46 +00:00
def get_src(src: Path, extension=None):
"""Get source files."""
if Path.is_file(src):
return [src]
# remove leading . from extension
extension = extension[1:] if extension.startswith(".") else extension
2021-07-26 14:05:55 +00:00
paths = list(
filter(
2021-08-02 17:48:27 +00:00
lambda x: not re.search(
"|".join([re.escape(x) for x in ignored_paths]), str(x)
),
2021-07-26 14:05:55 +00:00
list(src.glob(r"**/*.%s" % extension)),
)
)
2021-07-12 18:26:46 +00:00
if len(paths) == 0:
2021-07-23 20:58:24 +00:00
echo(Fore.BLUE + "No files to check! 😢")
2021-07-12 18:26:46 +00:00
return []
return paths
2021-07-13 17:25:29 +00:00
def build_output(error):
"""Build output for file errors."""
errors = sorted(list(error.values())[0], key=lambda x: int(x["line"].split(":")[0]))
2021-08-17 13:56:40 +00:00
width, _ = shutil.get_terminal_size()
2021-07-13 17:25:29 +00:00
if len(errors) == 0:
return 0
echo(
2021-07-30 19:31:34 +00:00
"{}\n{}\n{}{}".format(
Fore.GREEN + Style.BRIGHT,
list(error.keys())[0],
Style.DIM,
"".join(["" for x in range(1, width)]),
2021-07-13 17:25:29 +00:00
)
+ Style.RESET_ALL
)
for message in errors:
error = bool(message["code"][:1] == "E")
echo(
"{} {} {} {} {}".format(
(Fore.RED if error else Fore.YELLOW),
message["code"] + Style.RESET_ALL,
Fore.BLUE + message["line"] + Style.RESET_ALL,
message["message"],
Fore.BLUE + message["match"],
),
err=False,
)
return len(errors)
2021-07-23 20:58:24 +00:00
def build_check_output(errors, quiet):
"""Build output for reformat check."""
if len(errors) == 0:
return 0
color = {"-": Fore.YELLOW, "+": Fore.GREEN, "@": Style.BRIGHT + Fore.BLUE}
2021-08-17 13:56:40 +00:00
width, _ = shutil.get_terminal_size()
2021-07-23 20:58:24 +00:00
if quiet is True and len(list(errors.values())[0]) > 0:
2021-07-23 20:58:24 +00:00
echo(
Fore.GREEN
+ Style.BRIGHT
+ str(list(errors.keys())[0])
+ Style.DIM
+ Style.RESET_ALL
)
elif quiet is False and len(list(errors.values())[0]) > 0:
2021-07-23 20:58:24 +00:00
echo(
2021-07-30 19:31:34 +00:00
"{}\n{}\n{}{}".format(
Fore.GREEN + Style.BRIGHT,
list(errors.keys())[0],
Style.DIM,
"".join(["" for x in range(1, width)]),
2021-07-23 20:58:24 +00:00
)
+ Style.RESET_ALL
)
2021-07-30 19:31:34 +00:00
for diff in list(errors.values())[0][2:]:
2021-07-23 20:58:24 +00:00
echo(
"{}{}{}".format(
color.get(diff[:1], Style.RESET_ALL), diff, Style.RESET_ALL
),
err=False,
)
return len(list(filter(lambda x: len(x) > 0, errors.values())))
def build_quantity(size: int):
"""Count files in a list."""
2021-08-17 13:56:40 +00:00
return "%d file%s" % (size, ("s" if size > 1 or size == 0 else ""))
2021-07-23 20:58:24 +00:00
def build_quantity_tense(size: int):
"""Count files in a list."""
return "%d file%s %s" % (
size,
("s" if size > 1 or size == 0 else ""),
("were" if size > 1 or size == 0 else "was"),
)
2021-07-12 18:26:46 +00:00
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.argument(
"src",
type=click.Path(
exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
),
nargs=1,
metavar="SRC ...",
)
@click.option(
"-e",
"--extension",
type=str,
default="html",
help="File extension to lint",
show_default=True,
)
@click.option(
"-i",
"--ignore",
type=str,
default="",
help='Codes to ignore. ex: "W013,W014"',
show_default=False,
)
2021-07-23 20:58:24 +00:00
@click.option(
"--reformat",
is_flag=True,
2021-07-29 18:41:34 +00:00
help="Reformat the file(s).",
2021-07-23 20:58:24 +00:00
)
@click.option(
"--check",
is_flag=True,
2021-07-29 18:41:34 +00:00
help="Check formatting on the file(s).",
2021-07-23 20:58:24 +00:00
)
@click.option(
"--quiet",
is_flag=True,
2021-07-29 18:41:34 +00:00
help="Do not print diff when reformatting.",
2021-07-23 20:58:24 +00:00
)
def main(
src: str, extension: str, ignore: str, reformat: bool, check: bool, quiet: bool
):
2021-07-12 18:26:46 +00:00
"""Djlint django template files."""
file_list = get_src(Path(src), extension)
if len(file_list) == 0:
return
2021-07-23 20:58:24 +00:00
file_quantity = build_quantity(len(file_list))
2021-07-12 19:40:08 +00:00
2021-07-26 14:05:55 +00:00
message = "Lint"
if check is True:
message = "Check"
elif reformat is True:
message = "Reformatt"
2021-07-23 20:58:24 +00:00
2021-08-03 15:03:26 +00:00
bar_message = (
"{}{}{} {}{{n_fmt}}/{{total_fmt}}{} {}files{} {{bar}} {}{{elapsed}}{}".format(
Fore.BLUE + Style.BRIGHT,
message + "ing",
Style.RESET_ALL,
Fore.RED + Style.BRIGHT,
Style.RESET_ALL,
Fore.BLUE + Style.BRIGHT,
Style.RESET_ALL,
Fore.GREEN + Style.BRIGHT,
Style.RESET_ALL + " ",
)
2021-07-23 20:58:24 +00:00
)
2021-07-12 19:40:08 +00:00
echo()
2021-07-12 18:26:46 +00:00
worker_count = os.cpu_count()
if sys.platform == "win32":
# Work around https://bugs.python.org/issue26903
worker_count = min(worker_count, 60)
with ProcessPoolExecutor(max_workers=worker_count) as exe:
file_errors = []
2021-07-29 18:41:34 +00:00
if reformat is True or check is True:
2021-07-23 20:58:24 +00:00
func = partial(reformat_file, check)
futures = {
exe.submit(func, this_file): this_file for this_file in file_list
}
2021-07-23 20:58:24 +00:00
else:
func = partial(lint_file, ignore)
futures = {
exe.submit(func, this_file): this_file for this_file in file_list
}
2021-07-30 19:31:34 +00:00
elapsed = "00:00"
with tqdm(
total=len(file_list),
bar_format=bar_message,
colour="BLUE",
2021-07-30 19:31:34 +00:00
ascii="┈━",
leave=False,
) as pbar:
2021-07-30 19:31:34 +00:00
for future in as_completed(futures):
futures[future]
file_errors.append(future.result())
pbar.update()
2021-07-30 19:31:34 +00:00
elapsed = pbar.format_interval(pbar.format_dict["elapsed"])
finshed_bar_message = (
"{}{}{} {}{{n_fmt}}/{{total_fmt}}{} {}files{} {{bar}} {}{}{} ".format(
Fore.BLUE + Style.BRIGHT,
message + "ing",
Style.RESET_ALL,
Fore.GREEN + Style.BRIGHT,
Style.RESET_ALL,
Fore.BLUE + Style.BRIGHT,
Style.RESET_ALL,
Fore.GREEN + Style.BRIGHT,
elapsed,
Style.RESET_ALL,
)
)
asdf = tqdm(
total=len(file_list),
initial=len(file_list),
bar_format=finshed_bar_message,
colour="GREEN",
2021-07-30 20:18:38 +00:00
ascii="┈━",
2021-07-30 19:31:34 +00:00
leave=True,
)
asdf.close()
2021-07-12 18:26:46 +00:00
# format errors
2021-07-23 20:58:24 +00:00
success_message = ""
2021-07-12 19:40:08 +00:00
error_count = 0
echo()
2021-07-12 18:26:46 +00:00
2021-07-29 18:41:34 +00:00
if reformat is True or check is True:
# reformat message
2021-07-23 20:58:24 +00:00
for error in file_errors:
error_count += build_check_output(error, quiet)
tense_message = (
build_quantity(error_count) + " would be"
if check is True
else build_quantity_tense(error_count)
)
success_message = "%s updated." % tense_message
2021-07-12 18:26:46 +00:00
2021-07-29 18:41:34 +00:00
else:
# lint message
for error in file_errors:
error_count += build_output(error)
success_message = "%sed %s, found %d errors." % (
message,
file_quantity,
error_count,
)
2021-07-30 19:31:34 +00:00
success_color = Fore.RED + Style.BRIGHT if error_count > 0 else Fore.BLUE
echo(f"\n{success_color}{success_message}{Style.RESET_ALL}\n")
2021-07-12 18:26:46 +00:00
if __name__ == "__main__":
init(autoreset=True)
main()
deinit()