diff options
author | Jan Holthuis <jan.holthuis@ruhr-uni-bochum.de> | 2020-07-31 18:19:12 +0200 |
---|---|---|
committer | Jan Holthuis <jan.holthuis@ruhr-uni-bochum.de> | 2020-07-31 18:52:56 +0200 |
commit | 1d77aa3032e8575712821149d82e594920f05cee (patch) | |
tree | 96686112e5703469e5cb92e76548182080fee36e /tools/clang_format.py | |
parent | 0cf2ee06970022f14b3a76b7c354191840c67415 (diff) |
tools: Refactor python clang-format wrapping
This merges the clang-format and line-length hooks into a single one.
Semantically, this makes way more sense. Also, this prevent multiple
attempted commit that are rejected by pre-commit (pre-commit might skip
the line-length hook if old clang-format hook already failed).
Also, this removes any dependency on git-clang-format and implements the
whole mechanism itself. It retrieves all added lines from the unified diff
instead and is now capable of taking PRE_COMMIT_FROM_REF into account.
By moving the git-related code into a separate githelper library, it's
way easier to write additional python wrappers for new hooks, e.g. for
clang-tidy.
Diffstat (limited to 'tools/clang_format.py')
-rwxr-xr-x | tools/clang_format.py | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/tools/clang_format.py b/tools/clang_format.py new file mode 100755 index 0000000000..4447cb70aa --- /dev/null +++ b/tools/clang_format.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import argparse +import logging +import os +import re +import subprocess +import sys +import tempfile +import typing + +import githelper + + +# We recommend a maximum line length of 80, but do allow up to 100 characters +# if deemed necessary by the developer. Lines that exceed that limit will +# be wrapped after 80 characters automatically. +LINE_LENGTH_THRESHOLD = 100 +BREAK_BEFORE = 80 + + +def get_clang_format_config_with_columnlimit(rootdir, limit): + cpp_file = os.path.join(rootdir, "src/mixxx.cpp") + proc = subprocess.run( + ["clang-format", "--dump-config", cpp_file], + capture_output=True, + text=True, + ) + proc.check_returncode() + return re.sub(r"(ColumnLimit:\s*)\d+", r"\g<1>{}".format(80), proc.stdout,) + + +def run_clang_format_on_lines(rootdir, changed_file, assume_filename=None): + logger = logging.getLogger(__name__) + + line_arguments = [ + "--lines={}:{}".format(start, end) for start, end in changed_file.lines + ] + assert line_arguments + + logger.info("Reformatting %s...", changed_file.filename) + filename = os.path.join(rootdir, changed_file.filename) + cmd = [ + "clang-format", + "--style=file", + "--assume-filename={}".format( + assume_filename if assume_filename else filename + ), + *line_arguments, + ] + + with open(filename) as fp: + logger.debug("Executing: %r", cmd) + proc = subprocess.run(cmd, stdin=fp, capture_output=True, text=True) + try: + proc.check_returncode() + except subprocess.CalledProcessError: + logger.error( + "Error while executing command %s: %s", cmd, proc.stderr, + ) + raise + + if proc.stderr: + logger.error(proc.stderr) + with open(filename, mode="w+") as fp: + fp.write(proc.stdout) + + +def main(argv: typing.Optional[typing.List[str]] = None) -> int: + logging.basicConfig( + format="[%(levelname)s] %(message)s", level=logging.INFO + ) + + logger = logging.getLogger(__name__) + + parser = argparse.ArgumentParser() + parser.add_argument("--from-ref", help="compare against git reference") + parser.add_argument("files", nargs="*", help="only check these files") + args = parser.parse_args(argv) + + if not args.from_ref: + args.from_ref = os.getenv("PRE_COMMIT_FROM_REF") or os.getenv( + "PRE_COMMIT_SOURCE" + ) + + # Filter filenames + rootdir = githelper.get_toplevel_path() + + # First pass: Format added lines using clang-format + logger.info("First pass: Reformat files...") + files_with_added_lines = githelper.get_changed_lines_grouped( + from_ref=args.from_ref, + filter_lines=lambda line: line.added, + include_files=args.files, + ) + for changed_file in files_with_added_lines: + run_clang_format_on_lines(rootdir, changed_file) + + # Second pass: Wrap long added lines using clang-format + logger.info("Second pass: Reformat files with long lines...") + files_with_long_added_lines = githelper.get_changed_lines_grouped( + from_ref=args.from_ref, + filter_lines=lambda line: line.added + and LINE_LENGTH_THRESHOLD < (len(line.text) - 1), + include_files=args.files, + ) + config = get_clang_format_config_with_columnlimit(rootdir, BREAK_BEFORE) + with tempfile.TemporaryDirectory(prefix="clang-format") as tempdir: + # Create temporary config with ColumnLimit enabled + configfile = os.path.join(tempdir, ".clang-format") + with open(configfile, mode="w") as configfp: + configfp.write(config) + + for changed_file in files_with_long_added_lines: + run_clang_format_on_lines( + rootdir, + changed_file, + assume_filename=os.path.join(tempdir, changed_file.filename), + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) |