From cb49c157a55430245f9f7a8f14a1aaebce78522d Mon Sep 17 00:00:00 2001 From: q0w <43147888+q0w@users.noreply.github.com> Date: Sun, 4 Jun 2023 15:43:49 +0300 Subject: [PATCH] feat: Add `toml-sort` linter (#69) https://github.com/pappasam/toml-sort --- .../toml_sort_linter/.lintrunner.toml | 21 +++ .../adapters/toml_sort_linter.py | 143 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 examples/adapters/toml_sort_linter/.lintrunner.toml create mode 100644 lintrunner_adapters/adapters/toml_sort_linter.py diff --git a/examples/adapters/toml_sort_linter/.lintrunner.toml b/examples/adapters/toml_sort_linter/.lintrunner.toml new file mode 100644 index 0000000..601e171 --- /dev/null +++ b/examples/adapters/toml_sort_linter/.lintrunner.toml @@ -0,0 +1,21 @@ +[[linter]] +code = 'TOML-SORT' +include_patterns = ['**/*.toml'] +exclude_patterns = [] +command = [ + 'python', + '-m', + 'lintrunner_adapters', + 'run', + 'toml_sort_linter', + '@{{PATHSFILE}}' +] +init_command = [ + 'python', + '-m', + 'lintrunner_adapters', + 'run', + 'pip_init', + '--dry-run={{DRYRUN}}', + 'toml-sort==0.23.1', +] diff --git a/lintrunner_adapters/adapters/toml_sort_linter.py b/lintrunner_adapters/adapters/toml_sort_linter.py new file mode 100644 index 0000000..d536b10 --- /dev/null +++ b/lintrunner_adapters/adapters/toml_sort_linter.py @@ -0,0 +1,143 @@ +"""Adapter for https://github.com/pappasam/toml-sort.""" + +from __future__ import annotations + +import argparse +import concurrent.futures +import logging +import os +import subprocess +import sys + +from lintrunner_adapters import ( + LintMessage, + LintSeverity, + add_default_options, + as_posix, + run_command, +) + +LINTER_CODE = "TOML-SORT" + + +def check_file( + filename: str, + retries: int, + timeout: int, +) -> list[LintMessage]: + try: + with open(filename, "rb") as f: + original = f.read() + with open(filename, "rb") as f: + proc = run_command( + ["toml-sort", "-"], + stdin=f, + retries=retries, + timeout=timeout, + check=True, + ) + except subprocess.TimeoutExpired: + return [ + LintMessage( + path=filename, + line=None, + char=None, + code=LINTER_CODE, + severity=LintSeverity.ERROR, + name="timeout", + original=None, + replacement=None, + description="toml-sort timed out while trying to process a file.", + ) + ] + except (OSError, subprocess.CalledProcessError) as err: + return [ + LintMessage( + path=filename, + line=None, + char=None, + code=LINTER_CODE, + severity=LintSeverity.ADVICE, + name="command-failed", + original=None, + replacement=None, + description=( + f"Failed due to {err.__class__.__name__}:\n{err}" + if not isinstance(err, subprocess.CalledProcessError) + else ( + f"COMMAND (exit code {err.returncode})\n" + f"{' '.join(as_posix(x) for x in err.cmd)}\n\n" + f"STDERR\n{err.stderr.decode('utf-8').strip() or '(empty)'}\n\n" + f"STDOUT\n{err.stdout.decode('utf-8').strip() or '(empty)'}" + ) + ), + ) + ] + + replacement = proc.stdout + if original == replacement: + return [] + + return [ + LintMessage( + path=filename, + line=None, + char=None, + code=LINTER_CODE, + severity=LintSeverity.WARNING, + name="format", + original=original.decode("utf-8"), + replacement=replacement.decode("utf-8"), + description="Run `lintrunner -a` to apply this patch.", + ) + ] + + +def main() -> None: + parser = argparse.ArgumentParser( + description=f"toml-sort wrapper linter. Linter code: {LINTER_CODE}", + fromfile_prefix_chars="@", + ) + parser.add_argument( + "--timeout", + default=90, + type=int, + help="seconds to wait for toml-sort", + ) + add_default_options(parser) + args = parser.parse_args() + + logging.basicConfig( + format="<%(threadName)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.ThreadPoolExecutor( + max_workers=os.cpu_count(), + thread_name_prefix="Thread", + ) as executor: + futures = { + executor.submit( + check_file, + x, + args.retries, + args.timeout, + ): x + for x in args.filenames + } + for future in concurrent.futures.as_completed(futures): + try: + for lint_message in future.result(): + lint_message.display() + except Exception: + logging.critical('Failed at "%s".', futures[future]) + raise + + +if __name__ == "__main__": + main()