clang-format-diff.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. #!/usr/bin/python
  2. #
  3. #===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
  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. r"""
  12. ClangFormat Diff Reformatter
  13. ============================
  14. This script reads input from a unified diff and reformats all the changed
  15. lines. This is useful to reformat all the lines touched by a specific patch.
  16. Example usage for git users:
  17. git diff -U0 HEAD^ | clang-format-diff.py -p1 -i
  18. """
  19. import argparse
  20. import difflib
  21. import re
  22. import string
  23. import subprocess
  24. import StringIO
  25. import sys
  26. # Change this to the full path if clang-format is not on the path.
  27. binary = 'clang-format'
  28. def main():
  29. parser = argparse.ArgumentParser(description=
  30. 'Reformat changed lines in diff. Without -i '
  31. 'option just output the diff that would be '
  32. 'introduced.')
  33. parser.add_argument('-i', action='store_true', default=False,
  34. help='apply edits to files instead of displaying a diff')
  35. parser.add_argument('-p', default=0,
  36. help='strip the smallest prefix containing P slashes')
  37. parser.add_argument(
  38. '-style',
  39. help=
  40. 'formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)')
  41. args = parser.parse_args()
  42. # Extract changed lines for each file.
  43. filename = None
  44. lines_by_file = {}
  45. for line in sys.stdin:
  46. match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
  47. if match:
  48. filename = match.group(2)
  49. if filename == None:
  50. continue
  51. # FIXME: Add other types containing C++/ObjC code.
  52. if not (filename.endswith(".cpp") or filename.endswith(".cc") or
  53. filename.endswith(".h")):
  54. continue
  55. match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
  56. if match:
  57. start_line = int(match.group(1))
  58. line_count = 1
  59. if match.group(3):
  60. line_count = int(match.group(3))
  61. if line_count == 0:
  62. continue
  63. end_line = start_line + line_count - 1;
  64. lines_by_file.setdefault(filename, []).extend(
  65. ['-lines', str(start_line) + ':' + str(end_line)])
  66. # Reformat files containing changes in place.
  67. for filename, lines in lines_by_file.iteritems():
  68. command = [binary, filename]
  69. if args.i:
  70. command.append('-i')
  71. command.extend(lines)
  72. if args.style:
  73. command.extend(['-style', args.style])
  74. p = subprocess.Popen(command, stdout=subprocess.PIPE,
  75. stderr=None, stdin=subprocess.PIPE)
  76. stdout, stderr = p.communicate()
  77. if p.returncode != 0:
  78. sys.exit(p.returncode);
  79. if not args.i:
  80. with open(filename) as f:
  81. code = f.readlines()
  82. formatted_code = StringIO.StringIO(stdout).readlines()
  83. diff = difflib.unified_diff(code, formatted_code,
  84. filename, filename,
  85. '(before formatting)', '(after formatting)')
  86. diff_string = string.join(diff, '')
  87. if len(diff_string) > 0:
  88. sys.stdout.write(diff_string)
  89. if __name__ == '__main__':
  90. main()