#!/usr/bin/python """djLint · lint and reformat HTML templates.""" import os import sys import tempfile from concurrent.futures import ProcessPoolExecutor, as_completed from functools import partial from pathlib import Path from typing import Dict, List, Optional import click from click import echo from colorama import Fore, Style, colorama_text from tqdm import tqdm from .lint import lint_file from .output import print_output from .reformat import reformat_file from .settings import Config from .src import get_src @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, required=True, metavar="SRC ...", ) @click.version_option(package_name="djlint") @click.option( "-e", "--extension", type=str, default="", help="File extension to check [default: html]", show_default=False, ) @click.option( "-i", "--ignore", type=str, default="", help='Codes to ignore. ex: "H014,H017"', show_default=False, ) @click.option( "--reformat", is_flag=True, help="Reformat the file(s).", ) @click.option( "--check", is_flag=True, help="Check formatting on the file(s).", ) @click.option( "--indent", type=int, help="Indent spacing. [default: 4]", show_default=False, ) @click.option( "--quiet", is_flag=True, help="Do not print diff when reformatting.", ) @click.option( "--profile", type=str, help="Enable defaults by template language. ops: django, jinja, nunjucks, handlebars, golang, angular, html [default: html]", ) @click.option( "--require-pragma", is_flag=True, 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]", ) @click.option( "--use-gitignore", is_flag=True, help="Use .gitignore file to extend excludes.", ) @click.option( "--warn", is_flag=True, help="Return errors as warnings.", ) @colorama_text(autoreset=True) def main( src: List[str], extension: str, ignore: str, reformat: bool, indent: Optional[int], check: bool, quiet: bool, profile: str, require_pragma: bool, lint: bool, use_gitignore: bool, warn: bool, ) -> None: """djLint · HTML template linter and formatter.""" config = Config( src[0], extension=extension, ignore=ignore, indent=indent, quiet=quiet, profile=profile, require_pragma=require_pragma, lint=lint or not (reformat or check), reformat=reformat, check=check, use_gitignore=use_gitignore, warn=warn, ) temp_file = None if "-" in src: stdin_stream = click.get_text_stream("stdin", encoding="utf8") stdin_text = stdin_stream.read() temp_file = tempfile.NamedTemporaryFile(delete=False) temp_file.write(str.encode(stdin_text)) temp_file.seek(0) # cannot use gitignore for stdin paths. config.use_gitignore = False file_list = get_src([Path(temp_file.name)], config) else: file_list = get_src([Path(x) for x in src], config) if len(file_list) == 0: return message = "" if config.check is True: message = "Checking" elif config.reformat is True: message = "Reformatting" if config.lint: if message != "": message += " and " message += "Linting" # pylint: disable=C0209 bar_message = ( "{}{}{} {}{{n_fmt}}/{{total_fmt}}{} {}files{} {{bar}} {}{{elapsed}}{}".format( Fore.BLUE + Style.BRIGHT, message, Style.RESET_ALL, Fore.RED + Style.BRIGHT, Style.RESET_ALL, Fore.BLUE + Style.BRIGHT, Style.RESET_ALL, Fore.GREEN + Style.BRIGHT, Style.RESET_ALL + " ", ) ) if config.stdin is False or config.lint: echo() worker_count = os.cpu_count() or 1 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 = [] 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: elapsed = "00:00" with tqdm( total=len(file_list), bar_format=bar_message, colour="BLUE", ascii="┈━", leave=False, ) as pbar: for future in as_completed(futures): file_errors.append(future.result()) pbar.update() elapsed = pbar.format_interval(pbar.format_dict["elapsed"]) finshed_bar_message = "{}{}{} {}{{n_fmt}}/{{total_fmt}}{} {}files{} {{bar}} {}{}{} ".format( Fore.BLUE + Style.BRIGHT, message, 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, ) finished_bar = tqdm( total=len(file_list), initial=len(file_list), bar_format=finshed_bar_message, colour="GREEN", ascii="┈━", leave=True, ) finished_bar.close() if temp_file and (config.reformat or config.check): # if using stdin, only give back formatted code. echo(Path(temp_file.name).read_text(encoding="utf8").rstrip().encode('utf8')) if temp_file: temp_file.close() os.unlink(temp_file.name) if bool(print_output(config, file_errors, len(file_list))) and config.warn is False: sys.exit(1) 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