Skip to content

Instantly share code, notes, and snippets.

@goldsborough
Created February 7, 2018 07:16
Show Gist options
  • Save goldsborough/a004efc8717911f06c000b83e6c75989 to your computer and use it in GitHub Desktop.
Save goldsborough/a004efc8717911f06c000b83e6c75989 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
import argparse
import json
import re
import subprocess
import sys
DEFAULT_FILE_PATTERN = r'.*\.[ch](pp)?'
# @@ -start,count +start,count @@
CHUNK_PATTERN = r'^@@\s+-\d+,\d+\s+\+(\d+),(\d+)\s+@@'
def transform_globs_into_regexes(globs):
return [glob.replace('*', '.*').replace('?', '.') for glob in globs]
def get_file_patterns(globs, regexes):
regexes += transform_globs_into_regexes(globs)
if not regexes:
regexes = [DEFAULT_FILE_PATTERN]
return [re.compile(regex) for regex in regexes]
def git_diff(args):
command = ['git', '--no-pager', 'diff', '--no-color'] + args
try:
return subprocess.check_output(command, stderr=subprocess.STDOUT)
except OSError:
_, e, _ = sys.exc_info()
raise RuntimeError('Error executing git diff: {}'.format(e))
def get_changed_files(revision, paths, file_patterns):
args = [
'--diff-filter',
'AMU',
'--ignore-all-space',
'--name-only',
revision,
]
output = git_diff(args + paths)
files = []
for line in output.split('\n'):
for pattern in file_patterns:
if pattern.match(line):
files.append(line)
return files
def get_changed_lines(revision, filename):
output = git_diff(['--unified=0', revision, filename])
changed_lines = []
for chunk in re.finditer(CHUNK_PATTERN, output, re.MULTILINE):
start = int(chunk.group(1))
count = int(chunk.group(2))
changed_lines.append([start, start + count])
return dict(name=filename, lines=changed_lines)
def run_clang_tidy(clang_tidy_path, compile_commands_directory, extra_args,
line_filters):
command = ['clang-tidy', '-p', compile_commands_directory]
if line_filters:
command.append("--line-filter='{}'".format(json.dumps(line_filters)))
try:
output = subprocess.check_output(command + extra_args)
except OSError:
_, e, _ = sys.exc_info()
raise RuntimeError('Error executing clang-tidy: {}'.format(e))
else:
return output
def parse_options():
parser = argparse.ArgumentParser(
description='Run Clang-Tidy on your Git changes')
parser.add_argument('-a', '--all', action='store_true')
parser.add_argument('-c', '--clang-tidy-path', default='clang-tidy')
parser.add_argument('--extra-args', nargs='+', default=[])
parser.add_argument('-g', '--glob', nargs='+', default=[])
parser.add_argument('-x', '--regex', nargs='+', default=[])
parser.add_argument('-d', '--compile-commands-dir', default='.')
parser.add_argument('-r', '--revision', default='HEAD')
parser.add_argument('-p', '--paths', nargs='+', default=['.'])
return parser.parse_args()
def main():
options = parse_options()
file_patterns = get_file_patterns(options.glob, options.regex)
files = get_changed_files(options.revision, options.paths, file_patterns)
line_filters = []
if not options.all:
for filename in files:
line_filters.append(get_changed_lines(options.revision, filename))
result = run_clang_tidy(options.clang_tidy_path,
options.compile_commands_dir, options.extra_args,
line_filters)
print(result)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment