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
|
2021-09-16 11:36:44 +00:00
|
|
|
import tempfile
|
2021-07-30 17:29:07 +00:00
|
|
|
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
|
2021-09-28 11:04:04 +00:00
|
|
|
from typing import List, Optional
|
2021-07-12 18:26:46 +00:00
|
|
|
|
|
|
|
|
import click
|
|
|
|
|
from click import echo
|
|
|
|
|
from colorama import Fore, Style, deinit, init
|
2021-07-30 17:29:07 +00:00
|
|
|
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
|
2021-09-08 08:46:18 +00:00
|
|
|
from .settings import Config
|
2021-07-12 18:26:46 +00:00
|
|
|
|
|
|
|
|
|
2021-09-21 18:04:57 +00:00
|
|
|
def get_src(src: List[Path], config: Config) -> List[Path]:
|
2021-07-12 18:26:46 +00:00
|
|
|
"""Get source files."""
|
2021-09-21 18:04:57 +00:00
|
|
|
paths = []
|
|
|
|
|
for item in src:
|
|
|
|
|
if Path.is_file(item):
|
|
|
|
|
paths.append(item)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# remove leading . from extension
|
|
|
|
|
extension = str(config.extension)
|
|
|
|
|
extension = extension[1:] if extension.startswith(".") else extension
|
|
|
|
|
|
|
|
|
|
paths.extend(
|
|
|
|
|
filter(
|
|
|
|
|
lambda x: not re.search(config.exclude, str(x), re.VERBOSE),
|
|
|
|
|
list(item.glob(f"**/*.{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-09-08 08:46:18 +00:00
|
|
|
def build_output(error: dict) -> int:
|
2021-07-13 17:25:29 +00:00
|
|
|
"""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-09-17 08:25:01 +00:00
|
|
|
f"{Fore.GREEN}{Style.BRIGHT}\n{list(error.keys())[0]}\n{Style.DIM}"
|
|
|
|
|
+ "".join(["─" for x in range(1, width)])
|
2021-07-13 17:25:29 +00:00
|
|
|
+ Style.RESET_ALL
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for message in errors:
|
|
|
|
|
echo(
|
2021-09-17 08:25:01 +00:00
|
|
|
(Fore.RED if bool(message["code"][:1] == "E") else Fore.YELLOW)
|
|
|
|
|
+ message["code"]
|
|
|
|
|
+ Style.RESET_ALL
|
|
|
|
|
+ Fore.BLUE
|
|
|
|
|
+ " "
|
|
|
|
|
+ message["line"]
|
|
|
|
|
+ Style.RESET_ALL
|
|
|
|
|
+ " "
|
|
|
|
|
+ message["message"]
|
|
|
|
|
+ Fore.BLUE
|
|
|
|
|
+ " "
|
|
|
|
|
+ message["match"],
|
2021-07-13 17:25:29 +00:00
|
|
|
err=False,
|
|
|
|
|
)
|
|
|
|
|
return len(errors)
|
|
|
|
|
|
|
|
|
|
|
2021-09-08 08:46:18 +00:00
|
|
|
def build_check_output(errors: dict, quiet: bool) -> int:
|
2021-07-23 20:58:24 +00:00
|
|
|
"""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
|
|
|
|
2021-07-30 17:29:07 +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
|
|
|
|
|
)
|
|
|
|
|
|
2021-07-30 17:29:07 +00:00
|
|
|
elif quiet is False and len(list(errors.values())[0]) > 0:
|
2021-07-23 20:58:24 +00:00
|
|
|
echo(
|
2021-09-17 08:25:01 +00:00
|
|
|
Fore.GREEN
|
|
|
|
|
+ Style.BRIGHT
|
|
|
|
|
+ "\n"
|
|
|
|
|
+ str(list(errors.keys())[0])
|
|
|
|
|
+ "\n"
|
|
|
|
|
+ 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(
|
2021-09-17 08:25:01 +00:00
|
|
|
f"{ color.get(diff[:1], Style.RESET_ALL)}{diff}{Style.RESET_ALL}",
|
2021-07-23 20:58:24 +00:00
|
|
|
err=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return len(list(filter(lambda x: len(x) > 0, errors.values())))
|
|
|
|
|
|
|
|
|
|
|
2021-09-08 08:46:18 +00:00
|
|
|
def build_quantity(size: int) -> str:
|
2021-07-23 20:58:24 +00:00
|
|
|
"""Count files in a list."""
|
2021-09-17 08:25:01 +00:00
|
|
|
return str(size) + " file" + ("s" if size > 1 or size == 0 else "")
|
2021-07-23 20:58:24 +00:00
|
|
|
|
|
|
|
|
|
2021-09-08 08:46:18 +00:00
|
|
|
def build_quantity_tense(size: int) -> str:
|
2021-07-23 20:58:24 +00:00
|
|
|
"""Count files in a list."""
|
2021-09-17 08:25:01 +00:00
|
|
|
return (
|
|
|
|
|
str(size)
|
|
|
|
|
+ " file"
|
|
|
|
|
+ ("s" if size > 1 or size == 0 else "")
|
|
|
|
|
+ " "
|
|
|
|
|
+ ("were" if size > 1 or size == 0 else "was")
|
2021-07-23 20:58:24 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
),
|
2021-09-21 18:04:57 +00:00
|
|
|
nargs=-1,
|
|
|
|
|
required=True,
|
2021-07-12 18:26:46 +00:00
|
|
|
metavar="SRC ...",
|
|
|
|
|
)
|
2021-09-17 07:36:52 +00:00
|
|
|
@click.version_option(package_name="djlint")
|
2021-07-12 18:26:46 +00:00
|
|
|
@click.option(
|
|
|
|
|
"-e",
|
|
|
|
|
"--extension",
|
|
|
|
|
type=str,
|
2021-09-08 08:46:18 +00:00
|
|
|
default="",
|
2021-07-12 18:26:46 +00:00
|
|
|
help="File extension to lint",
|
|
|
|
|
show_default=True,
|
|
|
|
|
)
|
2021-07-30 17:29:07 +00:00
|
|
|
@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
|
|
|
)
|
2021-09-28 11:04:04 +00:00
|
|
|
@click.option(
|
|
|
|
|
"--indent",
|
2021-09-28 11:42:13 +00:00
|
|
|
type=int,
|
|
|
|
|
help="Indent spacing. ex: 3",
|
2021-09-28 11:04:04 +00:00
|
|
|
)
|
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
|
|
|
)
|
2021-07-30 17:29:07 +00:00
|
|
|
def main(
|
2021-09-21 18:04:57 +00:00
|
|
|
src: List[str],
|
|
|
|
|
extension: str,
|
|
|
|
|
ignore: str,
|
|
|
|
|
reformat: bool,
|
2021-09-28 11:42:13 +00:00
|
|
|
indent: Optional[int],
|
2021-09-21 18:04:57 +00:00
|
|
|
check: bool,
|
|
|
|
|
quiet: bool,
|
2021-09-08 08:46:18 +00:00
|
|
|
) -> None:
|
2021-09-20 09:49:31 +00:00
|
|
|
"""djLint · lint and reformat HTML templates."""
|
2021-09-28 11:04:04 +00:00
|
|
|
config = Config(
|
|
|
|
|
src[0], extension=extension, ignore=ignore, indent=indent, quiet=quiet
|
|
|
|
|
)
|
2021-09-08 08:46:18 +00:00
|
|
|
|
2021-09-16 11:36:44 +00:00
|
|
|
temp_file = None
|
|
|
|
|
|
2021-09-22 06:54:28 +00:00
|
|
|
if "-" in src:
|
2021-09-16 11:36:44 +00:00
|
|
|
stdin_stream = click.get_text_stream("stdin")
|
|
|
|
|
stdin_text = stdin_stream.read()
|
|
|
|
|
|
2021-09-16 11:47:25 +00:00
|
|
|
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
2021-09-16 11:36:44 +00:00
|
|
|
temp_file.write(str.encode(stdin_text))
|
|
|
|
|
temp_file.seek(0)
|
|
|
|
|
|
2021-09-21 18:04:57 +00:00
|
|
|
file_list = get_src([Path(temp_file.name)], config)
|
2021-09-16 11:36:44 +00:00
|
|
|
|
|
|
|
|
else:
|
2021-09-21 18:04:57 +00:00
|
|
|
file_list = get_src([Path(x) for x in src], config)
|
2021-07-12 18:26:46 +00:00
|
|
|
|
|
|
|
|
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-09-17 08:25:01 +00:00
|
|
|
# pylint: disable=C0209
|
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
|
|
|
|
2021-07-30 17:29:07 +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:
|
2021-07-30 17:29:07 +00:00
|
|
|
file_errors = []
|
2021-07-29 18:41:34 +00:00
|
|
|
if reformat is True or check is True:
|
2021-09-08 08:46:18 +00:00
|
|
|
func = partial(reformat_file, config, check)
|
2021-07-30 17:29:07 +00:00
|
|
|
futures = {
|
|
|
|
|
exe.submit(func, this_file): this_file for this_file in file_list
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 20:58:24 +00:00
|
|
|
else:
|
2021-09-08 08:46:18 +00:00
|
|
|
func = partial(lint_file, config)
|
2021-07-30 17:29:07 +00:00
|
|
|
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"
|
2021-07-30 17:29:07 +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,
|
2021-07-30 17:29:07 +00:00
|
|
|
) as pbar:
|
2021-07-30 19:31:34 +00:00
|
|
|
|
2021-07-30 17:29:07 +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,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2021-08-19 21:36:33 +00:00
|
|
|
finished_bar = tqdm(
|
2021-07-30 19:31:34 +00:00
|
|
|
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,
|
|
|
|
|
)
|
2021-08-19 21:36:33 +00:00
|
|
|
finished_bar.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
|
2021-07-30 17:29:07 +00:00
|
|
|
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)
|
|
|
|
|
)
|
2021-09-17 08:25:01 +00:00
|
|
|
success_message = f"{tense_message} updated."
|
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)
|
|
|
|
|
|
2021-08-17 15:00:56 +00:00
|
|
|
error_case = "error" if error_count == 1 else "errors"
|
2021-09-17 08:25:01 +00:00
|
|
|
success_message = (
|
|
|
|
|
f"{message}ed {file_quantity}, found {error_count} {error_case}."
|
2021-07-29 18:41:34 +00:00
|
|
|
)
|
|
|
|
|
|
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
|
|
|
|
2021-09-16 11:36:44 +00:00
|
|
|
if temp_file:
|
|
|
|
|
temp_file.close()
|
2021-09-16 11:47:25 +00:00
|
|
|
os.unlink(temp_file.name)
|
2021-09-16 11:36:44 +00:00
|
|
|
|
2021-08-19 15:16:41 +00:00
|
|
|
if bool(error_count):
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
2021-07-12 18:26:46 +00:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
init(autoreset=True)
|
|
|
|
|
main()
|
|
|
|
|
deinit()
|