brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.2 KiB · 3059982 Raw
198 lines · python
1#!/usr/bin/env python32#3# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#4#5# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.6# See https://llvm.org/LICENSE.txt for license information.7# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception8#9# ===------------------------------------------------------------------------===#10 11"""12This script reads input from a unified diff and reformats all the changed13lines. This is useful to reformat all the lines touched by a specific patch.14Example usage for git/svn users:15 16  git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i17  svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i18 19It should be noted that the filename contained in the diff is used unmodified20to determine the source file to update. Users calling this script directly21should be careful to ensure that the path in the diff is correct relative to the22current working directory.23"""24from __future__ import absolute_import, division, print_function25 26import argparse27import difflib28import re29import subprocess30import sys31 32if sys.version_info.major >= 3:33    from io import StringIO34else:35    from io import BytesIO as StringIO36 37 38def main():39    parser = argparse.ArgumentParser(40        description=__doc__.format(clang_format_diff="%(prog)s"),41        formatter_class=argparse.RawDescriptionHelpFormatter,42    )43    parser.add_argument(44        "-i",45        action="store_true",46        default=False,47        help="apply edits to files instead of displaying a diff",48    )49    parser.add_argument(50        "-p",51        metavar="NUM",52        default=0,53        help="strip the smallest prefix containing P slashes",54    )55    parser.add_argument(56        "-regex",57        metavar="PATTERN",58        default=None,59        help="custom pattern selecting file paths to reformat "60        "(case sensitive, overrides -iregex)",61    )62    parser.add_argument(63        "-iregex",64        metavar="PATTERN",65        default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"66        r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|ipynb|s?vh?)",67        help="custom pattern selecting file paths to reformat "68        "(case insensitive, overridden by -regex)",69    )70    parser.add_argument(71        "-sort-includes",72        action="store_true",73        default=False,74        help="let clang-format sort include blocks",75    )76    parser.add_argument(77        "-v",78        "--verbose",79        action="store_true",80        help="be more verbose, ineffective without -i",81    )82    parser.add_argument(83        "-style",84        help="formatting style to apply (LLVM, GNU, Google, Chromium, "85        "Microsoft, Mozilla, WebKit)",86    )87    parser.add_argument(88        "-fallback-style",89        help="The name of the predefined style used as a"90        "fallback in case clang-format is invoked with"91        "-style=file, but can not find the .clang-format"92        "file to use.",93    )94    parser.add_argument(95        "-binary",96        default="clang-format",97        help="location of binary to use for clang-format",98    )99    args = parser.parse_args()100 101    # Extract changed lines for each file.102    filename = None103    lines_by_file = {}104    for line in sys.stdin:105        match = re.search(r"^\+\+\+\ (.*?/){%s}(.+)" % args.p, line.rstrip())106        if match:107            filename = match.group(2)108        if filename is None:109            continue110 111        if args.regex is not None:112            if not re.match("^%s$" % args.regex, filename):113                continue114        else:115            if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):116                continue117 118        match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line)119        if match:120            start_line = int(match.group(1))121            line_count = 1122            if match.group(2):123                line_count = int(match.group(2))124                # The input is something like125                #126                # @@ -1, +0,0 @@127                #128                # which means no lines were added.129                if line_count == 0:130                    continue131            # Also format lines range if line_count is 0 in case of deleting132            # surrounding statements.133            end_line = start_line134            if line_count != 0:135                end_line += line_count - 1136            lines_by_file.setdefault(filename, []).extend(137                ["--lines", str(start_line) + ":" + str(end_line)]138            )139 140    # Reformat files containing changes in place.141    has_diff = False142    for filename, lines in lines_by_file.items():143        if args.i and args.verbose:144            print("Formatting {}".format(filename))145        command = [args.binary, filename]146        if args.i:147            command.append("-i")148        if args.sort_includes:149            command.append("--sort-includes")150        command.extend(lines)151        if args.style:152            command.extend(["--style", args.style])153        if args.fallback_style:154            command.extend(["--fallback-style", args.fallback_style])155 156        try:157            p = subprocess.Popen(158                command,159                stdout=subprocess.PIPE,160                stderr=None,161                stdin=subprocess.PIPE,162                universal_newlines=True,163            )164        except OSError as e:165            # Give the user more context when clang-format isn't166            # found/isn't executable, etc.167            raise RuntimeError(168                'Failed to run "%s" - %s"' % (" ".join(command), e.strerror)169            )170 171        stdout, _stderr = p.communicate()172        if p.returncode != 0:173            return p.returncode174 175        if not args.i:176            with open(filename) as f:177                code = f.readlines()178            formatted_code = StringIO(stdout).readlines()179            diff = difflib.unified_diff(180                code,181                formatted_code,182                filename,183                filename,184                "(before formatting)",185                "(after formatting)",186            )187            diff_string = "".join(diff)188            if len(diff_string) > 0:189                has_diff = True190                sys.stdout.write(diff_string)191 192    if has_diff:193        return 1194 195 196if __name__ == "__main__":197    sys.exit(main())198