#!/usr/bin/env python3 from helpers import ( print_error_for_file, get_output, git_ls_files, filter_changed, get_binary, ) import argparse import click import colorama import multiprocessing import os import queue import re import subprocess import sys import threading def run_format(executable, args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() invocation = [executable] if args.inplace: invocation.append("-i") else: invocation.extend(["--dry-run", "-Werror"]) invocation.append(path) proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stderr) failed_files.append(path) queue.task_done() def progress_bar_show(value): return value if value is not None else "" def main(): colorama.init() parser = argparse.ArgumentParser() parser.add_argument( "-j", "--jobs", type=int, default=multiprocessing.cpu_count(), help="number of format instances to be run in parallel.", ) parser.add_argument( "files", nargs="*", default=[], help="files to be processed (regex on path)" ) parser.add_argument( "-i", "--inplace", action="store_true", help="reformat files in-place" ) parser.add_argument( "-c", "--changed", action="store_true", help="only run on changed files" ) args = parser.parse_args() files = [] for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: files = filter_changed(files) files.sort() failed_files = [] try: executable = get_binary("clang-format", 13) task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): t = threading.Thread( target=run_format, args=(executable, args, task_queue, lock, failed_files) ) t.daemon = True t.start() # Fill the queue with files. with click.progressbar( files, width=30, file=sys.stderr, item_show_func=progress_bar_show ) as bar: for name in bar: task_queue.put(name) # Wait for all threads to be done. task_queue.join() except FileNotFoundError as ex: return 1 except KeyboardInterrupt: print() print("Ctrl-C detected, goodbye.") # Kill subprocesses (and ourselves!) # No simple, clean alternative appears to be available. os.kill(0, 9) return 2 # Will not execute. return len(failed_files) if __name__ == "__main__": sys.exit(main())