Daniel Jasper | 7c4a9a0 | 2013-03-20 09:53:23 +0000 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # |
| 3 | #===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===# |
| 4 | # |
| 5 | # The LLVM Compiler Infrastructure |
| 6 | # |
| 7 | # This file is distributed under the University of Illinois Open Source |
| 8 | # License. See LICENSE.TXT for details. |
| 9 | # |
| 10 | #===------------------------------------------------------------------------===# |
| 11 | |
| 12 | r""" |
| 13 | ClangFormat Diff Reformatter |
| 14 | ============================ |
| 15 | |
| 16 | This script reads input from a unified diff and reformats all the changed |
| 17 | lines. This is useful to reformat all the lines touched by a specific patch. |
| 18 | Example usage for git users: |
| 19 | |
| 20 | git diff -U0 HEAD^ | clang-format-diff.py -p1 |
| 21 | |
| 22 | """ |
| 23 | |
| 24 | import argparse |
| 25 | import re |
| 26 | import subprocess |
| 27 | import sys |
| 28 | |
| 29 | |
| 30 | # Change this to the full path if clang-format is not on the path. |
| 31 | binary = 'clang-format' |
| 32 | |
| 33 | |
| 34 | def getOffsetLength(filename, line_number, line_count): |
| 35 | """ |
| 36 | Calculates the field offset and length based on line number and count. |
| 37 | """ |
| 38 | offset = 0 |
| 39 | length = 0 |
| 40 | with open(filename, 'r') as f: |
| 41 | for line in f: |
| 42 | if line_number > 1: |
| 43 | offset += len(line) |
| 44 | line_number -= 1 |
| 45 | elif line_count > 0: |
| 46 | length += len(line) |
| 47 | line_count -= 1 |
| 48 | else: |
| 49 | break |
| 50 | return offset, length |
| 51 | |
| 52 | |
| 53 | def formatRange(r, style): |
| 54 | """ |
| 55 | Formats range 'r' according to style 'style'. |
| 56 | """ |
| 57 | filename, line_number, line_count = r |
| 58 | # FIXME: Add other types containing C++/ObjC code. |
| 59 | if not (filename.endswith(".cpp") or filename.endswith(".cc") or |
| 60 | filename.endswith(".h")): |
| 61 | return |
| 62 | |
| 63 | offset, length = getOffsetLength(filename, line_number, line_count) |
| 64 | with open(filename, 'r') as f: |
| 65 | text = f.read() |
Daniel Jasper | 6391183 | 2013-04-09 15:23:04 +0000 | [diff] [blame] | 66 | command = [binary, '-offset', str(offset), '-length', str(length)] |
| 67 | if style: |
Daniel Jasper | 7a9ed44 | 2013-04-12 13:42:36 +0000 | [diff] [blame] | 68 | command.extend(['-style', style]) |
Daniel Jasper | 6391183 | 2013-04-09 15:23:04 +0000 | [diff] [blame] | 69 | p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
Daniel Jasper | 7c4a9a0 | 2013-03-20 09:53:23 +0000 | [diff] [blame] | 70 | stdin=subprocess.PIPE) |
| 71 | stdout, stderr = p.communicate(input=text) |
| 72 | if stderr: |
| 73 | print stderr |
| 74 | return |
| 75 | if not stdout: |
| 76 | print 'Segfault occurred while formatting', filename |
| 77 | print 'Please report a bug on llvm.org/bugs.' |
| 78 | return |
| 79 | with open(filename, 'w') as f: |
| 80 | f.write(stdout) |
| 81 | |
| 82 | |
| 83 | def main(): |
| 84 | parser = argparse.ArgumentParser(description= |
Alexander Kornienko | 4e65c98 | 2013-09-02 16:39:23 +0000 | [diff] [blame] | 85 | 'Reformat changed lines in diff.') |
Daniel Jasper | 01970ef | 2013-05-30 11:50:20 +0000 | [diff] [blame] | 86 | parser.add_argument('-p', default=0, |
Daniel Jasper | 7c4a9a0 | 2013-03-20 09:53:23 +0000 | [diff] [blame] | 87 | help='strip the smallest prefix containing P slashes') |
Alexander Kornienko | 4e65c98 | 2013-09-02 16:39:23 +0000 | [diff] [blame] | 88 | parser.add_argument( |
| 89 | '-style', |
| 90 | help= |
| 91 | 'formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)') |
Daniel Jasper | 7c4a9a0 | 2013-03-20 09:53:23 +0000 | [diff] [blame] | 92 | args = parser.parse_args() |
| 93 | |
| 94 | filename = None |
| 95 | ranges = [] |
| 96 | |
| 97 | for line in sys.stdin: |
| 98 | match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) |
| 99 | if match: |
| 100 | filename = match.group(2) |
| 101 | if filename == None: |
| 102 | continue |
| 103 | |
| 104 | match = re.search('^@@.*\+(\d+)(,(\d+))?', line) |
| 105 | if match: |
| 106 | line_count = 1 |
| 107 | if match.group(3): |
| 108 | line_count = int(match.group(3)) |
| 109 | ranges.append((filename, int(match.group(1)), line_count)) |
| 110 | |
| 111 | # Reverse the ranges so that the reformatting does not influence file offsets. |
| 112 | for r in reversed(ranges): |
| 113 | # Do the actual formatting. |
| 114 | formatRange(r, args.style) |
| 115 | |
| 116 | |
| 117 | if __name__ == '__main__': |
| 118 | main() |