1#!/usr/bin/env python3 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 io 35import sys 36from shutil import which 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( 63 '--skip-javadoc-formatting', 64 action='store_true', 65 default=False, 66 help='do not reformat javadoc') 67 parser.add_argument('-b', '--binary', help='path to google-java-format binary') 68 parser.add_argument('--google-java-format-jar', metavar='ABSOLUTE_PATH', default=None, 69 help='use a custom google-java-format jar') 70 71 args = parser.parse_args() 72 73 # Extract changed lines for each file. 74 filename = None 75 lines_by_file = {} 76 77 for line in sys.stdin: 78 match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) 79 if match: 80 filename = match.group(2) 81 if filename == None: 82 continue 83 84 if args.regex is not None: 85 if not re.match('^%s$' % args.regex, filename): 86 continue 87 else: 88 if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): 89 continue 90 91 match = re.search('^@@.*\+(\d+)(,(\d+))?', line) 92 if match: 93 start_line = int(match.group(1)) 94 line_count = 1 95 if match.group(3): 96 line_count = int(match.group(3)) 97 if line_count == 0: 98 continue 99 end_line = start_line + line_count - 1; 100 lines_by_file.setdefault(filename, []).extend( 101 ['-lines', str(start_line) + ':' + str(end_line)]) 102 103 if args.binary: 104 base_command = [args.binary] 105 elif args.google_java_format_jar: 106 base_command = ['java', '-jar', args.google_java_format_jar] 107 else: 108 binary = which('google-java-format') or '/usr/bin/google-java-format' 109 base_command = [binary] 110 111 # Reformat files containing changes in place. 112 for filename, lines in lines_by_file.items(): 113 if args.i and args.verbose: 114 print('Formatting', filename) 115 command = base_command[:] 116 if args.i: 117 command.append('-i') 118 if args.aosp: 119 command.append('--aosp') 120 if args.skip_sorting_imports: 121 command.append('--skip-sorting-imports') 122 if args.skip_removing_unused_imports: 123 command.append('--skip-removing-unused-imports') 124 if args.skip_javadoc_formatting: 125 command.append('--skip-javadoc-formatting') 126 command.extend(lines) 127 command.append(filename) 128 p = subprocess.Popen(command, stdout=subprocess.PIPE, 129 stderr=None, stdin=subprocess.PIPE) 130 stdout, stderr = p.communicate() 131 if p.returncode != 0: 132 sys.exit(p.returncode); 133 134 if not args.i: 135 with open(filename) as f: 136 code = f.readlines() 137 formatted_code = io.StringIO(stdout.decode('utf-8')).readlines() 138 diff = difflib.unified_diff(code, formatted_code, 139 filename, filename, 140 '(before formatting)', '(after formatting)') 141 diff_string = ''.join(diff) 142 if len(diff_string) > 0: 143 sys.stdout.write(diff_string) 144 145if __name__ == '__main__': 146 main() 147