Home | History | Annotate | Download | only in google-java-format
      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 """
     13 google-java-format Diff Reformatter
     14 ============================
     15 
     16 This script reads input from a unified diff and reformats all the changed
     17 lines. This is useful to reformat all the lines touched by a specific patch.
     18 Example 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 
     23 """
     24 
     25 import argparse
     26 import difflib
     27 import re
     28 import string
     29 import subprocess
     30 import StringIO
     31 import sys
     32 from distutils.spawn import find_executable
     33 
     34 binary = find_executable('google-java-format') or '/usr/bin/google-java-format'
     35 
     36 def main():
     37   parser = argparse.ArgumentParser(description=
     38                                    'Reformat changed lines in diff. Without -i '
     39                                    'option just output the diff that would be '
     40                                    'introduced.')
     41   parser.add_argument('-i', action='store_true', default=False,
     42                       help='apply edits to files instead of displaying a diff')
     43 
     44   parser.add_argument('-p', metavar='NUM', default=0,
     45                       help='strip the smallest prefix containing P slashes')
     46   parser.add_argument('-regex', metavar='PATTERN', default=None,
     47                       help='custom pattern selecting file paths to reformat '
     48                       '(case sensitive, overrides -iregex)')
     49   parser.add_argument('-iregex', metavar='PATTERN', default=r'.*\.java',
     50                       help='custom pattern selecting file paths to reformat '
     51                       '(case insensitive, overridden by -regex)')
     52   parser.add_argument('-v', '--verbose', action='store_true',
     53                       help='be more verbose, ineffective without -i')
     54   parser.add_argument('-a', '--aosp', action='store_true',
     55                       help='use AOSP style instead of Google Style (4-space indentation)')
     56   parser.add_argument('--skip-sorting-imports', action='store_true',
     57                       help='do not fix the import order')
     58   args = parser.parse_args()
     59 
     60   # Extract changed lines for each file.
     61   filename = None
     62   lines_by_file = {}
     63 
     64   for line in sys.stdin:
     65     match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
     66     if match:
     67       filename = match.group(2)
     68     if filename == None:
     69       continue
     70 
     71     if args.regex is not None:
     72       if not re.match('^%s$' % args.regex, filename):
     73         continue
     74     else:
     75       if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
     76         continue
     77 
     78     match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
     79     if match:
     80       start_line = int(match.group(1))
     81       line_count = 1
     82       if match.group(3):
     83         line_count = int(match.group(3))
     84       if line_count == 0:
     85         continue
     86       end_line = start_line + line_count - 1;
     87       lines_by_file.setdefault(filename, []).extend(
     88           ['-lines', str(start_line) + ':' + str(end_line)])
     89 
     90   # Reformat files containing changes in place.
     91   for filename, lines in lines_by_file.iteritems():
     92     if args.i and args.verbose:
     93       print 'Formatting', filename
     94     command = [binary]
     95     if args.i:
     96       command.append('-i')
     97     if args.aosp:
     98       command.append('--aosp')
     99     if args.skip_sorting_imports:
    100       command.append('--skip-sorting-imports')
    101     command.extend(lines)
    102     command.append(filename)
    103     p = subprocess.Popen(command, stdout=subprocess.PIPE,
    104                          stderr=None, stdin=subprocess.PIPE)
    105     stdout, stderr = p.communicate()
    106     if p.returncode != 0:
    107       sys.exit(p.returncode);
    108 
    109     if not args.i:
    110       with open(filename) as f:
    111         code = f.readlines()
    112       formatted_code = StringIO.StringIO(stdout).readlines()
    113       diff = difflib.unified_diff(code, formatted_code,
    114                                   filename, filename,
    115                                   '(before formatting)', '(after formatting)')
    116       diff_string = string.join(diff, '')
    117       if len(diff_string) > 0:
    118         sys.stdout.write(diff_string)
    119 
    120 if __name__ == '__main__':
    121   main()
    122