xref: /aosp_15_r20/external/clang/tools/clang-format/clang-format-diff.py (revision 67e74705e28f6214e480b399dd47ea732279e315)
1*67e74705SXin Li#!/usr/bin/env python
2*67e74705SXin Li#
3*67e74705SXin Li#===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
4*67e74705SXin Li#
5*67e74705SXin Li#                     The LLVM Compiler Infrastructure
6*67e74705SXin Li#
7*67e74705SXin Li# This file is distributed under the University of Illinois Open Source
8*67e74705SXin Li# License. See LICENSE.TXT for details.
9*67e74705SXin Li#
10*67e74705SXin Li#===------------------------------------------------------------------------===#
11*67e74705SXin Li
12*67e74705SXin Lir"""
13*67e74705SXin LiClangFormat Diff Reformatter
14*67e74705SXin Li============================
15*67e74705SXin Li
16*67e74705SXin LiThis script reads input from a unified diff and reformats all the changed
17*67e74705SXin Lilines. This is useful to reformat all the lines touched by a specific patch.
18*67e74705SXin LiExample usage for git/svn users:
19*67e74705SXin Li
20*67e74705SXin Li  git diff -U0 HEAD^ | clang-format-diff.py -p1 -i
21*67e74705SXin Li  svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
22*67e74705SXin Li
23*67e74705SXin Li"""
24*67e74705SXin Li
25*67e74705SXin Liimport argparse
26*67e74705SXin Liimport difflib
27*67e74705SXin Liimport re
28*67e74705SXin Liimport string
29*67e74705SXin Liimport subprocess
30*67e74705SXin Liimport StringIO
31*67e74705SXin Liimport sys
32*67e74705SXin Li
33*67e74705SXin Li
34*67e74705SXin Lidef main():
35*67e74705SXin Li  parser = argparse.ArgumentParser(description=
36*67e74705SXin Li                                   'Reformat changed lines in diff. Without -i '
37*67e74705SXin Li                                   'option just output the diff that would be '
38*67e74705SXin Li                                   'introduced.')
39*67e74705SXin Li  parser.add_argument('-i', action='store_true', default=False,
40*67e74705SXin Li                      help='apply edits to files instead of displaying a diff')
41*67e74705SXin Li  parser.add_argument('-p', metavar='NUM', default=0,
42*67e74705SXin Li                      help='strip the smallest prefix containing P slashes')
43*67e74705SXin Li  parser.add_argument('-regex', metavar='PATTERN', default=None,
44*67e74705SXin Li                      help='custom pattern selecting file paths to reformat '
45*67e74705SXin Li                      '(case sensitive, overrides -iregex)')
46*67e74705SXin Li  parser.add_argument('-iregex', metavar='PATTERN', default=
47*67e74705SXin Li                      r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc|js|ts|proto'
48*67e74705SXin Li                      r'|protodevel|java)',
49*67e74705SXin Li                      help='custom pattern selecting file paths to reformat '
50*67e74705SXin Li                      '(case insensitive, overridden by -regex)')
51*67e74705SXin Li  parser.add_argument('-sort-includes', action='store_true', default=False,
52*67e74705SXin Li                      help='let clang-format sort include blocks')
53*67e74705SXin Li  parser.add_argument('-v', '--verbose', action='store_true',
54*67e74705SXin Li                      help='be more verbose, ineffective without -i')
55*67e74705SXin Li  parser.add_argument('-style',
56*67e74705SXin Li                      help='formatting style to apply (LLVM, Google, Chromium, '
57*67e74705SXin Li                      'Mozilla, WebKit)')
58*67e74705SXin Li  parser.add_argument('-binary', default='clang-format',
59*67e74705SXin Li                      help='location of binary to use for clang-format')
60*67e74705SXin Li  args = parser.parse_args()
61*67e74705SXin Li
62*67e74705SXin Li  # Extract changed lines for each file.
63*67e74705SXin Li  filename = None
64*67e74705SXin Li  lines_by_file = {}
65*67e74705SXin Li  for line in sys.stdin:
66*67e74705SXin Li    match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
67*67e74705SXin Li    if match:
68*67e74705SXin Li      filename = match.group(2)
69*67e74705SXin Li    if filename == None:
70*67e74705SXin Li      continue
71*67e74705SXin Li
72*67e74705SXin Li    if args.regex is not None:
73*67e74705SXin Li      if not re.match('^%s$' % args.regex, filename):
74*67e74705SXin Li        continue
75*67e74705SXin Li    else:
76*67e74705SXin Li      if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
77*67e74705SXin Li        continue
78*67e74705SXin Li
79*67e74705SXin Li    match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
80*67e74705SXin Li    if match:
81*67e74705SXin Li      start_line = int(match.group(1))
82*67e74705SXin Li      line_count = 1
83*67e74705SXin Li      if match.group(3):
84*67e74705SXin Li        line_count = int(match.group(3))
85*67e74705SXin Li      if line_count == 0:
86*67e74705SXin Li        continue
87*67e74705SXin Li      end_line = start_line + line_count - 1;
88*67e74705SXin Li      lines_by_file.setdefault(filename, []).extend(
89*67e74705SXin Li          ['-lines', str(start_line) + ':' + str(end_line)])
90*67e74705SXin Li
91*67e74705SXin Li  # Reformat files containing changes in place.
92*67e74705SXin Li  for filename, lines in lines_by_file.iteritems():
93*67e74705SXin Li    if args.i and args.verbose:
94*67e74705SXin Li      print 'Formatting', filename
95*67e74705SXin Li    command = [args.binary, filename]
96*67e74705SXin Li    if args.i:
97*67e74705SXin Li      command.append('-i')
98*67e74705SXin Li    if args.sort_includes:
99*67e74705SXin Li      command.append('-sort-includes')
100*67e74705SXin Li    command.extend(lines)
101*67e74705SXin Li    if args.style:
102*67e74705SXin Li      command.extend(['-style', args.style])
103*67e74705SXin Li    p = subprocess.Popen(command, stdout=subprocess.PIPE,
104*67e74705SXin Li                         stderr=None, stdin=subprocess.PIPE)
105*67e74705SXin Li    stdout, stderr = p.communicate()
106*67e74705SXin Li    if p.returncode != 0:
107*67e74705SXin Li      sys.exit(p.returncode);
108*67e74705SXin Li
109*67e74705SXin Li    if not args.i:
110*67e74705SXin Li      with open(filename) as f:
111*67e74705SXin Li        code = f.readlines()
112*67e74705SXin Li      formatted_code = StringIO.StringIO(stdout).readlines()
113*67e74705SXin Li      diff = difflib.unified_diff(code, formatted_code,
114*67e74705SXin Li                                  filename, filename,
115*67e74705SXin Li                                  '(before formatting)', '(after formatting)')
116*67e74705SXin Li      diff_string = string.join(diff, '')
117*67e74705SXin Li      if len(diff_string) > 0:
118*67e74705SXin Li        sys.stdout.write(diff_string)
119*67e74705SXin Li
120*67e74705SXin Liif __name__ == '__main__':
121*67e74705SXin Li  main()
122