• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Modified copy of clang-format-diff.py that works with yapf.
2#
3# Licensed under the Apache License, Version 2.0 (the "License") with LLVM
4# Exceptions; you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://llvm.org/LICENSE.txt
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""
15This script reads input from a unified diff and reformats all the changed
16lines. This is useful to reformat all the lines touched by a specific patch.
17Example usage for git/svn users:
18
19  git diff -U0 --no-color --relative HEAD^ | yapf-diff -i
20  svn diff --diff-cmd=diff -x-U0 | yapf-diff -p0 -i
21
22It should be noted that the filename contained in the diff is used unmodified
23to determine the source file to update. Users calling this script directly
24should be careful to ensure that the path in the diff is correct relative to the
25current working directory.
26"""
27
28import argparse
29import difflib
30import re
31import subprocess
32import sys
33from io import StringIO
34
35
36def main():
37  parser = argparse.ArgumentParser(
38      description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
39  parser.add_argument(
40      '-i',
41      '--in-place',
42      action='store_true',
43      default=False,
44      help='apply edits to files instead of displaying a diff')
45  parser.add_argument(
46      '-p',
47      '--prefix',
48      metavar='NUM',
49      default=1,
50      help='strip the smallest prefix containing P slashes')
51  parser.add_argument(
52      '--regex',
53      metavar='PATTERN',
54      default=None,
55      help='custom pattern selecting file paths to reformat '
56      '(case sensitive, overrides -iregex)')
57  parser.add_argument(
58      '--iregex',
59      metavar='PATTERN',
60      default=r'.*\.(py)',
61      help='custom pattern selecting file paths to reformat '
62      '(case insensitive, overridden by -regex)')
63  parser.add_argument(
64      '-v',
65      '--verbose',
66      action='store_true',
67      help='be more verbose, ineffective without -i')
68  parser.add_argument(
69      '--style',
70      help='specify formatting style: either a style name (for '
71      'example "pep8" or "google"), or the name of a file with '
72      'style settings. The default is pep8 unless a '
73      '.style.yapf or setup.cfg file located in one of the '
74      'parent directories of the source file (or current '
75      'directory for stdin)')
76  parser.add_argument(
77      '--binary', default='yapf', help='location of binary to use for yapf')
78  args = parser.parse_args()
79
80  # Extract changed lines for each file.
81  filename = None
82  lines_by_file = {}
83  for line in sys.stdin:
84    match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.prefix, line)
85    if match:
86      filename = match.group(2)
87    if filename is None:
88      continue
89
90    if args.regex is not None:
91      if not re.match('^%s$' % args.regex, filename):
92        continue
93    elif not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
94      continue
95
96    match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line)
97    if match:
98      start_line = int(match.group(1))
99      line_count = 1
100      if match.group(3):
101        line_count = int(match.group(3))
102      if line_count == 0:
103        continue
104      end_line = start_line + line_count - 1
105      lines_by_file.setdefault(filename, []).extend(
106          ['--lines', str(start_line) + '-' + str(end_line)])
107
108  # Reformat files containing changes in place.
109  for filename, lines in lines_by_file.items():
110    if args.in_place and args.verbose:
111      print('Formatting {}'.format(filename))
112    command = [args.binary, filename]
113    if args.in_place:
114      command.append('-i')
115    command.extend(lines)
116    if args.style:
117      command.extend(['--style', args.style])
118    p = subprocess.Popen(
119        command,
120        stdout=subprocess.PIPE,
121        stderr=None,
122        stdin=subprocess.PIPE,
123        universal_newlines=True)
124    stdout, stderr = p.communicate()
125    if p.returncode != 0:
126      sys.exit(p.returncode)
127
128    if not args.in_place:
129      with open(filename) as f:
130        code = f.readlines()
131      formatted_code = StringIO(stdout).readlines()
132      diff = difflib.unified_diff(code, formatted_code, filename, filename,
133                                  '(before formatting)', '(after formatting)')
134      diff_string = ''.join(diff)
135      if len(diff_string) > 0:
136        sys.stdout.write(diff_string)
137
138
139if __name__ == '__main__':
140  main()
141