djLint/src/djlint/__init__.py

251 lines
6.4 KiB
Python
Raw Normal View History

2021-07-12 18:26:46 +00:00
#!/usr/bin/python
2021-10-14 10:19:48 +00:00
"""djLint · lint and reformat HTML templates."""
2021-07-13 17:25:29 +00:00
2021-07-12 18:26:46 +00:00
import os
import sys
2021-09-16 11:36:44 +00:00
import tempfile
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-10-14 10:19:48 +00:00
from typing import Dict, List, Optional
2021-07-12 18:26:46 +00:00
import click
from click import echo
from colorama import Fore, Style, colorama_text
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
2021-10-14 10:19:48 +00:00
from .output import print_output
2021-07-29 18:41:34 +00:00
from .reformat import reformat_file
from .settings import Config
2021-10-14 10:19:48 +00:00
from .src import get_src
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 ...",
)
@click.version_option(package_name="djlint")
2021-07-12 18:26:46 +00:00
@click.option(
"-e",
"--extension",
type=str,
default="",
2021-10-05 06:34:35 +00:00
help="File extension to check [default: html]",
show_default=False,
2021-07-12 18:26:46 +00:00
)
@click.option(
"-i",
"--ignore",
type=str,
default="",
help='Codes to ignore. ex: "H014,H017"',
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,
2021-10-05 06:34:35 +00:00
help="Indent spacing. [default: 4]",
show_default=False,
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-09-30 08:02:05 +00:00
@click.option(
"--profile",
type=str,
2022-02-18 19:07:30 +00:00
help="Enable defaults by template language. ops: django, jinja, nunjucks, handlebars, golang, angular, html [default: html]",
2021-09-30 08:02:05 +00:00
)
@click.option(
"--require-pragma",
is_flag=True,
2021-10-14 10:19:48 +00:00
help="Only format or lint files that starts with a comment with the text 'djlint:on'",
)
@click.option(
"--lint",
is_flag=True,
help="Lint for common issues. [default option]",
)
2021-10-29 08:42:39 +00:00
@click.option(
"--use-gitignore",
is_flag=True,
2021-10-29 08:55:20 +00:00
help="Use .gitignore file to extend excludes.",
2021-10-29 08:42:39 +00:00
)
2022-03-14 14:24:28 +00:00
@click.option(
"--warn",
is_flag=True,
help="Return errors as warnings.",
)
@colorama_text(autoreset=True)
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-30 08:02:05 +00:00
profile: str,
2021-10-14 10:19:48 +00:00
require_pragma: bool,
lint: bool,
2021-10-29 08:42:39 +00:00
use_gitignore: bool,
2022-03-14 14:24:28 +00:00
warn: bool,
) -> None:
2021-12-01 09:13:09 +00:00
"""djLint · HTML template linter and formatter."""
2021-09-28 11:04:04 +00:00
config = Config(
2021-09-30 08:02:05 +00:00
src[0],
extension=extension,
ignore=ignore,
indent=indent,
quiet=quiet,
profile=profile,
require_pragma=require_pragma,
2021-10-14 10:19:48 +00:00
lint=lint or not (reformat or check),
reformat=reformat,
check=check,
2021-10-29 08:42:39 +00:00
use_gitignore=use_gitignore,
2022-03-14 14:24:28 +00:00
warn=warn,
2021-09-28 11:04:04 +00:00
)
2021-09-16 11:36:44 +00:00
temp_file = None
2021-09-22 06:54:28 +00:00
if "-" in src:
stdin_stream = click.get_text_stream("stdin", encoding="utf8")
2021-09-16 11:36:44 +00:00
stdin_text = stdin_stream.read()
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)
# cannot use gitignore for stdin paths.
config.use_gitignore = False
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-10-14 10:19:48 +00:00
message = ""
2021-07-12 19:40:08 +00:00
2021-10-14 10:19:48 +00:00
if config.check is True:
message = "Checking"
elif config.reformat is True:
message = "Reformatting"
2021-07-26 14:05:55 +00:00
2021-10-14 10:19:48 +00:00
if config.lint:
2021-10-14 10:30:24 +00:00
if message != "":
message += " and "
message += "Linting"
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,
2021-10-14 10:19:48 +00:00
message,
2021-08-03 15:03:26 +00:00
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-10-14 10:19:48 +00:00
if config.stdin is False or config.lint:
2021-10-05 11:11:08 +00:00
echo()
worker_count = os.cpu_count() or 1
2021-07-12 18:26:46 +00:00
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-10-14 10:19:48 +00:00
func = partial(process, config)
futures = {exe.submit(func, this_file): this_file for this_file in file_list}
if temp_file is None or config.lint:
2021-10-05 11:11:08 +00:00
elapsed = "00:00"
with tqdm(
total=len(file_list),
bar_format=bar_message,
colour="BLUE",
ascii="┈━",
leave=False,
) as pbar:
2021-07-30 19:31:34 +00:00
2021-10-05 11:11:08 +00:00
for future in as_completed(futures):
2021-10-05 11:11:08 +00:00
file_errors.append(future.result())
pbar.update()
elapsed = pbar.format_interval(pbar.format_dict["elapsed"])
2021-07-30 19:31:34 +00:00
2021-10-05 11:11:08 +00:00
finshed_bar_message = "{}{}{} {}{{n_fmt}}/{{total_fmt}}{} {}files{} {{bar}} {}{}{} ".format(
2021-07-30 19:31:34 +00:00
Fore.BLUE + Style.BRIGHT,
2021-10-14 10:19:48 +00:00
message,
2021-07-30 19:31:34 +00:00
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-10-05 11:11:08 +00:00
finished_bar = tqdm(
total=len(file_list),
initial=len(file_list),
bar_format=finshed_bar_message,
colour="GREEN",
ascii="┈━",
leave=True,
)
finished_bar.close()
2021-10-14 10:19:48 +00:00
if temp_file and (config.reformat or config.check):
2021-10-05 11:11:08 +00:00
# if using stdin, only give back formatted code.
2021-10-14 10:19:48 +00:00
echo(Path(temp_file.name).read_text(encoding="utf8").rstrip())
2021-07-12 18:26:46 +00:00
2021-09-16 11:36:44 +00:00
if temp_file:
temp_file.close()
os.unlink(temp_file.name)
2021-09-16 11:36:44 +00:00
2022-03-14 14:24:28 +00:00
if bool(print_output(config, file_errors, len(file_list))) and config.warn is False:
2021-08-19 15:16:41 +00:00
sys.exit(1)
2021-07-12 18:26:46 +00:00
2021-10-14 10:19:48 +00:00
def process(config: Config, this_file: Path) -> Dict:
"""Run linter or formatter."""
output = {}
if config.reformat or config.check:
output["format_message"] = reformat_file(config, this_file)
if config.lint:
output["lint_message"] = lint_file(config, this_file)
return output