from __future__ import annotations import argparse import concurrent.futures import json import logging import os import sys from enum import Enum from typing import Any, NamedTuple IS_WINDOWS: bool = os.name == "nt" def eprint(*args: Any, **kwargs: Any) -> None: print(*args, file=sys.stderr, flush=True, **kwargs) class LintSeverity(str, Enum): ERROR = "error" WARNING = "warning" ADVICE = "advice" DISABLED = "disabled" class LintMessage(NamedTuple): path: str | None line: int | None char: int | None code: str severity: LintSeverity name: str original: str | None replacement: str | None description: str | None def check_file(filename: str) -> list[LintMessage]: with open(filename, "rb") as f: original = f.read().decode("utf-8") replacement = "" with open(filename) as f: lines = f.readlines() for line in lines: if len(line.strip()) > 0: replacement += line replacement += "\n" * 3 replacement = replacement[:-3] if replacement == original: return [] return [ LintMessage( path=filename, line=None, char=None, code="MERGE_CONFLICTLESS_CSV", severity=LintSeverity.WARNING, name="format", original=original, replacement=replacement, description="Run `lintrunner -a` to apply this patch.", ) ] def main() -> None: parser = argparse.ArgumentParser( description="Format csv files to have 3 lines of space between each line to prevent merge conflicts.", fromfile_prefix_chars="@", ) parser.add_argument( "--verbose", action="store_true", help="verbose logging", ) parser.add_argument( "filenames", nargs="+", help="paths to lint", ) args = parser.parse_args() logging.basicConfig( format="<%(processName)s:%(levelname)s> %(message)s", level=logging.NOTSET if args.verbose else logging.DEBUG if len(args.filenames) < 1000 else logging.INFO, stream=sys.stderr, ) with concurrent.futures.ProcessPoolExecutor( max_workers=os.cpu_count(), ) as executor: futures = {executor.submit(check_file, x): x for x in args.filenames} for future in concurrent.futures.as_completed(futures): try: for lint_message in future.result(): print(json.dumps(lint_message._asdict()), flush=True) except Exception: logging.critical('Failed at "%s".', futures[future]) raise if __name__ == "__main__": main()