1#!/usr/bin/env python
2
3# Check that the input file has no external include guards.
4# Returns with 0 exit code on success, 1 otherwise.
5
6import re
7import sys
8import subprocess
9
10def git(*args, **kwargs):
11    return subprocess.check_output(['git'] + list(args), **kwargs)
12
13def get_changed_paths(filter):
14    output = git('diff', '--cached', '--name-only', '-z', '--diff-filter='+filter)
15    return output.split('\0')[:-1] # remove trailing ''
16
17def get_against():
18    try:
19        head = git('rev-parse', '--verify', 'HEAD', stderr=None)
20    except subprocess.CalledProcessError:
21        # Initial commit: diff against an empty tree object
22        return '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
23    else:
24        return 'HEAD'
25
26against = get_against()
27
28success = True
29
30def croak(path, line, msg, *args):
31    global success
32    success = False
33    if path is not None:
34        sys.stderr.write("%s:%d: " % (path, line or 0))
35    sys.stderr.write(msg % args if args else msg)
36    if msg[-1] != '\n':
37        sys.stderr.write('\n')
38
39def check_filenames():
40    try:
41        allownonascii = git('config', '--get', '--bool', 'hooks.allownonascii')
42    except subprocess.CalledProcessError:
43        pass
44    else:
45        if allownonascii == 'true':
46            return
47
48    for path in get_changed_paths('ACR'):
49        try:
50            path.decode('ascii')
51        except UnicodeDecodeError:
52            croak(path, 0, "Non-ASCII file name")
53
54def check_whitespace():
55    try:
56        git('diff-index', '--check', '--cached', against, stderr=None)
57    except subprocess.CalledProcessError as e:
58        sys.stderr.write(e.output)
59        global success
60        success = False
61
62guard_re = re.compile('^[ \t]*#\s*ifndef\s+_.*?_H(PP)?\n'
63                      '\s*#\s*include\s+(".*?")\s*\n'
64                      '\s*#\s*endif.*?$',
65                      re.MULTILINE)
66
67def check_external_guards (infile):
68    contents = infile.read()
69    for m in guard_re.finditer(contents):
70        lineno = 1 + contents[:m.start()].count('\n')
71        croak(infile.name, lineno, "External include guard")
72        croak(None, None, m.group(0))
73
74def check_changed_files():
75    for path in get_changed_paths('AM'):
76        check_external_guards(open(path))
77
78def main():
79    check_filenames()
80    check_changed_files()
81    check_whitespace()
82    if not success:
83        exit(1)
84
85if __name__ == '__main__':
86    main()
87