djLint/src/djlint/output.py

221 lines
6.5 KiB
Python
Raw Normal View History

2021-10-14 10:19:48 +00:00
"""Build djLint console output."""
import shutil
from collections import Counter
2021-11-26 13:01:54 +00:00
from pathlib import Path
from typing import Any, Dict, List, Optional
2021-10-14 10:19:48 +00:00
2021-10-14 10:30:24 +00:00
import regex as re
2021-10-14 10:19:48 +00:00
from click import echo
from colorama import Fore, Style
from .settings import Config
try:
# this is used for windows + gitbash to set encoding correctly.
import sys
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined]
# pylint:disable=W0703
except BaseException:
pass
2021-10-14 10:19:48 +00:00
def print_output(
config: Config, file_errors: List[Dict[Any, Any]], file_count: int
) -> int:
"""Print results to console."""
file_quantity = build_quantity(file_count)
# format errors
reformat_success_message = ""
lint_success_message = ""
lint_error_count = 0
format_error_count = 0
print_blanks = config.stdin is False and config.quiet is False
if print_blanks:
2021-10-14 10:19:48 +00:00
echo()
for error in sorted(file_errors, key=lambda x: next(iter(list(x.values())[0]))):
if error.get("format_message") and config.stdin is False:
# reformat message
format_error_count += build_check_output(error["format_message"], config)
if error.get("lint_message"):
# lint message
lint_error_count += build_output(error["lint_message"], config)
2021-10-14 10:19:48 +00:00
if config.statistics and config.lint:
build_stats_output([x.get("lint_message") for x in file_errors], config)
2021-10-14 10:19:48 +00:00
tense_message = (
build_quantity(format_error_count) + " would be"
if config.check is True
else build_quantity_tense(format_error_count)
)
reformat_success_message = f"{tense_message} updated."
error_case = "error" if lint_error_count == 1 else "errors"
lint_success_message += (
f"Linted {file_quantity}, found {lint_error_count} {error_case}."
)
if print_blanks:
2021-10-14 10:19:48 +00:00
echo()
if (
config.quiet is False
and config.stdin is False
and (config.reformat or config.check)
):
2021-10-14 10:19:48 +00:00
reformat_success_color = (
Fore.RED + Style.BRIGHT if (format_error_count) > 0 else Fore.BLUE
)
echo(f"{reformat_success_color}{reformat_success_message}{Style.RESET_ALL}")
if config.lint and config.quiet is False:
2021-10-14 10:19:48 +00:00
lint_success_color = (
Fore.RED + Style.BRIGHT if (lint_error_count) > 0 else Fore.BLUE
)
echo(f"{lint_success_color}{lint_success_message}{Style.RESET_ALL}")
if print_blanks:
2021-10-14 10:19:48 +00:00
echo()
return lint_error_count + format_error_count
2021-11-26 13:01:54 +00:00
def build_relative_path(url: str, project_root: Path) -> str:
"""Get path relative to project."""
2021-11-29 08:26:36 +00:00
url_path = Path(url)
if project_root != url_path and project_root in url_path.parents:
return str(url_path.relative_to(project_root.resolve()))
2021-11-26 13:01:54 +00:00
return url
def build_output(error: dict, config: Config) -> int:
2021-10-14 10:19:48 +00:00
"""Build output for file errors."""
2021-10-28 10:55:20 +00:00
errors = sorted(
list(error.values())[0], key=lambda x: tuple(map(int, x["line"].split(":")))
)
2021-10-14 10:19:48 +00:00
width, _ = shutil.get_terminal_size()
if len(errors) == 0:
return 0
filename = build_relative_path(list(error.keys())[0], config.project_root.resolve())
2021-10-14 10:19:48 +00:00
if "{filename}" not in config.linter_output_format and not config.stdin:
2021-10-14 10:19:48 +00:00
echo(
f"{Fore.GREEN}{Style.BRIGHT}\n{filename}\n{Style.DIM}"
+ "".join(["" for x in range(1, width)])
+ Style.RESET_ALL
)
for message_dict in errors:
line = Fore.BLUE + message_dict["line"] + Style.RESET_ALL
code = (
2022-01-24 18:03:44 +00:00
(Fore.RED if message_dict["code"][:1] == "E" else Fore.YELLOW)
+ message_dict["code"]
2021-10-14 10:19:48 +00:00
+ Style.RESET_ALL
)
message = message_dict["message"]
match = (
Fore.BLUE
+ re.sub(r"\s{2,}|\n", " ", message_dict["match"])
2021-10-14 10:19:48 +00:00
+ Style.RESET_ALL
)
echo(
config.linter_output_format.format(
filename=filename, line=line, code=code, message=message, match=match
),
2021-10-14 10:19:48 +00:00
err=False,
)
2021-10-14 10:19:48 +00:00
return len(errors)
2021-11-26 13:01:54 +00:00
def build_check_output(errors: dict, config: Config) -> int:
2021-10-14 10:19:48 +00:00
"""Build output for reformat check."""
if len(errors) == 0:
return 0
color = {"-": Fore.YELLOW, "+": Fore.GREEN, "@": Style.BRIGHT + Fore.BLUE}
width, _ = shutil.get_terminal_size()
if config.quiet is False and len(list(errors.values())[0]) > 0:
2021-10-14 10:19:48 +00:00
echo(
Fore.GREEN
+ Style.BRIGHT
+ "\n"
2021-11-29 08:26:36 +00:00
+ build_relative_path(list(errors.keys())[0], config.project_root.resolve())
2021-10-14 10:19:48 +00:00
+ "\n"
+ Style.DIM
+ "".join(["" for x in range(1, width)])
+ Style.RESET_ALL
)
for diff in list(errors.values())[0][2:]:
echo(
f"{ 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) -> str:
"""Count files in a list."""
return str(size) + " file" + ("s" if size > 1 or size == 0 else "")
def build_quantity_tense(size: int) -> str:
"""Count files in a list."""
return (
str(size)
+ " file"
+ ("s" if size > 1 or size == 0 else "")
+ " "
+ ("were" if size > 1 or size == 0 else "was")
)
def build_stats_output(errors: List[Optional[Any]], config: Config) -> int:
"""Build output for linter statistics."""
if len(errors) == 0:
return 0
codes = []
for error in errors:
if error:
for code in list(error.values())[0]:
codes.append(code["code"])
messages = {
rule["rule"]["name"]: rule["rule"]["message"] for rule in config.linter_rules
}
echo()
width, _ = shutil.get_terminal_size()
echo(
f"{Fore.GREEN}{Style.BRIGHT}Statistics{Style.RESET_ALL}\n{Style.DIM}{'' * width}{Style.RESET_ALL}"
)
if messages and codes:
longest_code = len(max(messages.keys(), key=len))
longest_count = len(
str(max(Counter(codes).values(), key=lambda x: len(str(x))))
)
for code in sorted(Counter(codes).items()):
code_space = (longest_code - len(str(code[0]))) * " "
count_space = (longest_count - len(str(code[1]))) * " "
echo(
f"{Fore.YELLOW}{code[0]}{Fore.BLUE} {code_space}{code[1]}{Style.RESET_ALL} {count_space}{messages[code[0]]}"
)
return sum(Counter(codes).values())