xref: /openbsd-src/gnu/llvm/clang/tools/clang-format/clang-format-diff.py (revision 12c855180aad702bbcca06e0398d774beeafb155)
1a9ac8606Spatrick#!/usr/bin/env python3
2e5dd7070Spatrick#
3e5dd7070Spatrick#===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
4e5dd7070Spatrick#
5e5dd7070Spatrick# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6e5dd7070Spatrick# See https://llvm.org/LICENSE.txt for license information.
7e5dd7070Spatrick# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8e5dd7070Spatrick#
9e5dd7070Spatrick#===------------------------------------------------------------------------===#
10e5dd7070Spatrick
11e5dd7070Spatrick"""
12e5dd7070SpatrickThis script reads input from a unified diff and reformats all the changed
13e5dd7070Spatricklines. This is useful to reformat all the lines touched by a specific patch.
14e5dd7070SpatrickExample usage for git/svn users:
15e5dd7070Spatrick
16ec727ea7Spatrick  git diff -U0 --no-color --relative HEAD^ | clang-format-diff.py -p1 -i
17e5dd7070Spatrick  svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
18e5dd7070Spatrick
19ec727ea7SpatrickIt should be noted that the filename contained in the diff is used unmodified
20ec727ea7Spatrickto determine the source file to update. Users calling this script directly
21ec727ea7Spatrickshould be careful to ensure that the path in the diff is correct relative to the
22ec727ea7Spatrickcurrent working directory.
23e5dd7070Spatrick"""
24e5dd7070Spatrickfrom __future__ import absolute_import, division, print_function
25e5dd7070Spatrick
26e5dd7070Spatrickimport argparse
27e5dd7070Spatrickimport difflib
28e5dd7070Spatrickimport re
29e5dd7070Spatrickimport subprocess
30e5dd7070Spatrickimport sys
31e5dd7070Spatrick
32e5dd7070Spatrickif sys.version_info.major >= 3:
33e5dd7070Spatrick    from io import StringIO
34e5dd7070Spatrickelse:
35e5dd7070Spatrick    from io import BytesIO as StringIO
36e5dd7070Spatrick
37e5dd7070Spatrick
38e5dd7070Spatrickdef main():
39e5dd7070Spatrick  parser = argparse.ArgumentParser(description=__doc__,
40e5dd7070Spatrick                                   formatter_class=
41e5dd7070Spatrick                                           argparse.RawDescriptionHelpFormatter)
42e5dd7070Spatrick  parser.add_argument('-i', action='store_true', default=False,
43e5dd7070Spatrick                      help='apply edits to files instead of displaying a diff')
44e5dd7070Spatrick  parser.add_argument('-p', metavar='NUM', default=0,
45e5dd7070Spatrick                      help='strip the smallest prefix containing P slashes')
46e5dd7070Spatrick  parser.add_argument('-regex', metavar='PATTERN', default=None,
47e5dd7070Spatrick                      help='custom pattern selecting file paths to reformat '
48e5dd7070Spatrick                      '(case sensitive, overrides -iregex)')
49e5dd7070Spatrick  parser.add_argument('-iregex', metavar='PATTERN', default=
50*12c85518Srobert                      r'.*\.(cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp|hxx'
51*12c85518Srobert                      r'|m|mm|inc|js|ts|proto|protodevel|java|cs|json)',
52e5dd7070Spatrick                      help='custom pattern selecting file paths to reformat '
53e5dd7070Spatrick                      '(case insensitive, overridden by -regex)')
54e5dd7070Spatrick  parser.add_argument('-sort-includes', action='store_true', default=False,
55e5dd7070Spatrick                      help='let clang-format sort include blocks')
56e5dd7070Spatrick  parser.add_argument('-v', '--verbose', action='store_true',
57e5dd7070Spatrick                      help='be more verbose, ineffective without -i')
58e5dd7070Spatrick  parser.add_argument('-style',
59ec727ea7Spatrick                      help='formatting style to apply (LLVM, GNU, Google, Chromium, '
60ec727ea7Spatrick                      'Microsoft, Mozilla, WebKit)')
61*12c85518Srobert  parser.add_argument('-fallback-style',
62*12c85518Srobert                      help='The name of the predefined style used as a'
63*12c85518Srobert                      'fallback in case clang-format is invoked with'
64*12c85518Srobert                      '-style=file, but can not find the .clang-format'
65*12c85518Srobert                      'file to use.')
66e5dd7070Spatrick  parser.add_argument('-binary', default='clang-format',
67e5dd7070Spatrick                      help='location of binary to use for clang-format')
68e5dd7070Spatrick  args = parser.parse_args()
69e5dd7070Spatrick
70e5dd7070Spatrick  # Extract changed lines for each file.
71e5dd7070Spatrick  filename = None
72e5dd7070Spatrick  lines_by_file = {}
73e5dd7070Spatrick  for line in sys.stdin:
74e5dd7070Spatrick    match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
75e5dd7070Spatrick    if match:
76e5dd7070Spatrick      filename = match.group(2)
77ec727ea7Spatrick    if filename is None:
78e5dd7070Spatrick      continue
79e5dd7070Spatrick
80e5dd7070Spatrick    if args.regex is not None:
81e5dd7070Spatrick      if not re.match('^%s$' % args.regex, filename):
82e5dd7070Spatrick        continue
83e5dd7070Spatrick    else:
84e5dd7070Spatrick      if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
85e5dd7070Spatrick        continue
86e5dd7070Spatrick
87e5dd7070Spatrick    match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line)
88e5dd7070Spatrick    if match:
89e5dd7070Spatrick      start_line = int(match.group(1))
90e5dd7070Spatrick      line_count = 1
91e5dd7070Spatrick      if match.group(3):
92e5dd7070Spatrick        line_count = int(match.group(3))
93*12c85518Srobert      # Also format lines range if line_count is 0 in case of deleting
94*12c85518Srobert      # surrounding statements.
95*12c85518Srobert      end_line = start_line
96*12c85518Srobert      if line_count != 0:
97*12c85518Srobert        end_line += line_count - 1
98e5dd7070Spatrick      lines_by_file.setdefault(filename, []).extend(
99e5dd7070Spatrick          ['-lines', str(start_line) + ':' + str(end_line)])
100e5dd7070Spatrick
101e5dd7070Spatrick  # Reformat files containing changes in place.
102e5dd7070Spatrick  for filename, lines in lines_by_file.items():
103e5dd7070Spatrick    if args.i and args.verbose:
104e5dd7070Spatrick      print('Formatting {}'.format(filename))
105e5dd7070Spatrick    command = [args.binary, filename]
106e5dd7070Spatrick    if args.i:
107e5dd7070Spatrick      command.append('-i')
108e5dd7070Spatrick    if args.sort_includes:
109e5dd7070Spatrick      command.append('-sort-includes')
110e5dd7070Spatrick    command.extend(lines)
111e5dd7070Spatrick    if args.style:
112e5dd7070Spatrick      command.extend(['-style', args.style])
113*12c85518Srobert    if args.fallback_style:
114*12c85518Srobert      command.extend(['-fallback-style', args.fallback_style])
115a9ac8606Spatrick
116a9ac8606Spatrick    try:
117e5dd7070Spatrick      p = subprocess.Popen(command,
118e5dd7070Spatrick                           stdout=subprocess.PIPE,
119e5dd7070Spatrick                           stderr=None,
120e5dd7070Spatrick                           stdin=subprocess.PIPE,
121e5dd7070Spatrick                           universal_newlines=True)
122a9ac8606Spatrick    except OSError as e:
123a9ac8606Spatrick      # Give the user more context when clang-format isn't
124a9ac8606Spatrick      # found/isn't executable, etc.
125a9ac8606Spatrick      raise RuntimeError(
126a9ac8606Spatrick        'Failed to run "%s" - %s"' % (" ".join(command), e.strerror))
127a9ac8606Spatrick
128e5dd7070Spatrick    stdout, stderr = p.communicate()
129e5dd7070Spatrick    if p.returncode != 0:
130e5dd7070Spatrick      sys.exit(p.returncode)
131e5dd7070Spatrick
132e5dd7070Spatrick    if not args.i:
133e5dd7070Spatrick      with open(filename) as f:
134e5dd7070Spatrick        code = f.readlines()
135e5dd7070Spatrick      formatted_code = StringIO(stdout).readlines()
136e5dd7070Spatrick      diff = difflib.unified_diff(code, formatted_code,
137e5dd7070Spatrick                                  filename, filename,
138e5dd7070Spatrick                                  '(before formatting)', '(after formatting)')
139e5dd7070Spatrick      diff_string = ''.join(diff)
140e5dd7070Spatrick      if len(diff_string) > 0:
141e5dd7070Spatrick        sys.stdout.write(diff_string)
142e5dd7070Spatrick
143e5dd7070Spatrickif __name__ == '__main__':
144e5dd7070Spatrick  main()
145