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
|
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-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
|
2022-03-17 20:07:17 +00:00
|
|
|
from colorama import Fore, Style, colorama_text
|
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
|
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
|
2021-09-08 08:46:18 +00:00
|
|
|
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 ...",
|
|
|
|
|
)
|
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-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
|
|
|
)
|
2021-07-30 17:29:07 +00:00
|
|
|
@click.option(
|
|
|
|
|
"-i",
|
|
|
|
|
"--ignore",
|
|
|
|
|
type=str,
|
|
|
|
|
default="",
|
2021-10-01 07:40:41 +00:00
|
|
|
help='Codes to ignore. ex: "H014,H017"',
|
2021-07-30 17:29:07 +00:00
|
|
|
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
|
|
|
)
|
2021-10-13 14:26:53 +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-13 14:26:53 +00:00
|
|
|
)
|
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.",
|
|
|
|
|
)
|
2022-06-15 18:41:14 +00:00
|
|
|
@click.option(
|
|
|
|
|
"--preserve-leading-space",
|
|
|
|
|
is_flag=True,
|
|
|
|
|
help="Attempt to preserve leading space on text.",
|
|
|
|
|
)
|
2022-06-30 12:53:24 +00:00
|
|
|
@click.option(
|
|
|
|
|
"--preserve-blank-lines",
|
|
|
|
|
is_flag=True,
|
|
|
|
|
help="Attempt to preserve blank lines.",
|
|
|
|
|
)
|
2022-07-28 18:58:03 +00:00
|
|
|
@click.option(
|
|
|
|
|
"--format-css",
|
|
|
|
|
is_flag=True,
|
|
|
|
|
help="Also format contents of <style> tags.",
|
|
|
|
|
)
|
|
|
|
|
@click.option(
|
|
|
|
|
"--format-js",
|
|
|
|
|
is_flag=True,
|
|
|
|
|
help="Also format contents of <script> tags.",
|
|
|
|
|
)
|
2022-03-17 20:07:17 +00:00
|
|
|
@colorama_text(autoreset=True)
|
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-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,
|
2022-06-15 18:41:14 +00:00
|
|
|
preserve_leading_space: bool,
|
2022-06-30 12:53:24 +00:00
|
|
|
preserve_blank_lines: bool,
|
2022-07-28 18:58:03 +00:00
|
|
|
format_css: bool,
|
|
|
|
|
format_js: bool,
|
2021-09-08 08:46:18 +00:00
|
|
|
) -> 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,
|
2021-10-13 14:26:53 +00:00
|
|
|
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,
|
2022-06-15 18:41:14 +00:00
|
|
|
preserve_leading_space=preserve_leading_space,
|
2022-06-30 12:53:24 +00:00
|
|
|
preserve_blank_lines=preserve_blank_lines,
|
2022-07-28 18:58:03 +00:00
|
|
|
format_css=format_css,
|
|
|
|
|
format_js=format_js,
|
2021-09-28 11:04:04 +00:00
|
|
|
)
|
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:
|
2022-05-18 13:35:54 +00:00
|
|
|
stdin_stream = click.get_text_stream("stdin", encoding="utf8")
|
2021-09-16 11:36:44 +00:00
|
|
|
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)
|
|
|
|
|
|
2022-04-25 13:26:13 +00:00
|
|
|
# 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()
|
2021-07-30 17:29:07 +00:00
|
|
|
|
2021-10-08 10:55:16 +00:00
|
|
|
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:
|
2021-07-30 17:29:07 +00:00
|
|
|
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-07-30 17:29:07 +00:00
|
|
|
|
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.
|
2022-05-18 15:35:41 +00:00
|
|
|
echo(Path(temp_file.name).read_text(encoding="utf8").rstrip().encode("utf8"))
|
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
|
|
|
|
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
|