xref: /llvm-project/clang/tools/clang-format/clang-format-diff.py (revision e4549a2391a612e380d7362c2d75b729717c2d2c)
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
12r"""
13ClangFormat Diff Reformatter
14============================
15
16This script reads input from a unified diff and reformats all the changed
17lines. This is useful to reformat all the lines touched by a specific patch.
18Example usage for git users:
19
20  git diff -U0 HEAD^ | clang-format-diff.py -p1
21
22"""
23
24import argparse
25import re
26import subprocess
27import sys
28
29
30# Change this to the full path if clang-format is not on the path.
31binary = 'clang-format'
32
33
34def 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
53def 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()
66  command = [binary, '-offset', str(offset), '-length', str(length)]
67  if style:
68    command.append('-style', style)
69  p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
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
83def main():
84  parser = argparse.ArgumentParser(description=
85                                   'Reformat changed lines in diff')
86  parser.add_argument('-p', default=1,
87                      help='strip the smallest prefix containing P slashes')
88  parser.add_argument('-style', help='formatting style to apply (LLVM, Google)')
89  args = parser.parse_args()
90
91  filename = None
92  ranges = []
93
94  for line in sys.stdin:
95    match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
96    if match:
97      filename = match.group(2)
98    if filename == None:
99      continue
100
101    match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
102    if match:
103      line_count = 1
104      if match.group(3):
105        line_count = int(match.group(3))
106      ranges.append((filename, int(match.group(1)), line_count))
107
108  # Reverse the ranges so that the reformatting does not influence file offsets.
109  for r in reversed(ranges):
110    # Do the actual formatting.
111    formatRange(r, args.style)
112
113
114if __name__ == '__main__':
115  main()
116