1#!/usr/bin/env python2.7 2# 3#===- google-java-format-diff.py - google-java-format Diff Reformatter -----===# 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""" 13google-java-format 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/svn users: 19 20 git diff -U0 HEAD^ | google-java-format-diff.py -p1 -i 21 svn diff --diff-cmd=diff -x-U0 | google-java-format-diff.py -i 22 23For perforce users: 24 25 P4DIFF="git --no-pager diff --no-index" p4 diff | ./google-java-format-diff.py -i -p7 26 27""" 28 29import argparse 30import difflib 31import re 32import string 33import subprocess 34import StringIO 35import sys 36from distutils.spawn import find_executable 37 38def main(): 39 parser = argparse.ArgumentParser(description= 40 'Reformat changed lines in diff. Without -i ' 41 'option just output the diff that would be ' 42 'introduced.') 43 parser.add_argument('-i', action='store_true', default=False, 44 help='apply edits to files instead of displaying a diff') 45 46 parser.add_argument('-p', metavar='NUM', default=0, 47 help='strip the smallest prefix containing P slashes') 48 parser.add_argument('-regex', metavar='PATTERN', default=None, 49 help='custom pattern selecting file paths to reformat ' 50 '(case sensitive, overrides -iregex)') 51 parser.add_argument('-iregex', metavar='PATTERN', default=r'.*\.java', 52 help='custom pattern selecting file paths to reformat ' 53 '(case insensitive, overridden by -regex)') 54 parser.add_argument('-v', '--verbose', action='store_true', 55 help='be more verbose, ineffective without -i') 56 parser.add_argument('-a', '--aosp', action='store_true', 57 help='use AOSP style instead of Google Style (4-space indentation)') 58 parser.add_argument('--skip-sorting-imports', action='store_true', 59 help='do not fix the import order') 60 parser.add_argument('--skip-removing-unused-imports', action='store_true', 61 help='do not remove ununsed imports') 62 parser.add_argument('-b', '--binary', help='path to google-java-format binary') 63 parser.add_argument('--google-java-format-jar', metavar='ABSOLUTE_PATH', default=None, 64 help='use a custom google-java-format jar') 65 66 args = parser.parse_args() 67 68 # Extract changed lines for each file. 69 filename = None 70 lines_by_file = {} 71 72 for line in sys.stdin: 73 match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) 74 if match: 75 filename = match.group(2) 76 if filename == None: 77 continue 78 79 if args.regex is not None: 80 if not re.match('^%s$' % args.regex, filename): 81 continue 82 else: 83 if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): 84 continue 85 86 match = re.search('^@@.*\+(\d+)(,(\d+))?', line) 87 if match: 88 start_line = int(match.group(1)) 89 line_count = 1 90 if match.group(3): 91 line_count = int(match.group(3)) 92 if line_count == 0: 93 continue 94 end_line = start_line + line_count - 1; 95 lines_by_file.setdefault(filename, []).extend( 96 ['-lines', str(start_line) + ':' + str(end_line)]) 97 98 if args.binary: 99 base_command = [args.binary] 100 elif args.google_java_format_jar: 101 base_command = ['java', '-jar', args.google_java_format_jar] 102 else: 103 binary = find_executable('google-java-format') or '/usr/bin/google-java-format' 104 base_command = [binary] 105 106 # Reformat files containing changes in place. 107 for filename, lines in lines_by_file.iteritems(): 108 if args.i and args.verbose: 109 print 'Formatting', filename 110 command = base_command[:] 111 if args.i: 112 command.append('-i') 113 if args.aosp: 114 command.append('--aosp') 115 if args.skip_sorting_imports: 116 command.append('--skip-sorting-imports') 117 if args.skip_removing_unused_imports: 118 command.append('--skip-removing-unused-imports') 119 command.extend(lines) 120 command.append(filename) 121 p = subprocess.Popen(command, stdout=subprocess.PIPE, 122 stderr=None, stdin=subprocess.PIPE) 123 stdout, stderr = p.communicate() 124 if p.returncode != 0: 125 sys.exit(p.returncode); 126 127 if not args.i: 128 with open(filename) as f: 129 code = f.readlines() 130 formatted_code = StringIO.StringIO(stdout).readlines() 131 diff = difflib.unified_diff(code, formatted_code, 132 filename, filename, 133 '(before formatting)', '(after formatting)') 134 diff_string = string.join(diff, '') 135 if len(diff_string) > 0: 136 sys.stdout.write(diff_string) 137 138if __name__ == '__main__': 139 main() 140