.gitignore: add some more IDE / tools related file
[babeltrace.git] / tools / check-include-guard.py
CommitLineData
270aa17a
SM
1#!/usr/bin/env python3
2
3# Usage: check-include-guard.py [--fix] FILE
4#
5# Checks (and optionally tries to fix) the C/C++ header include guard
6# names and format of `FILE`.
7#
8# With `--fix`, this script fixes the include guard names in place.
9
10import re
11import sys
12import pathlib
13import argparse
14
15
16class _Oops(Exception):
17 def __init__(self, msg: str):
18 self._msg = msg
19
20 @property
21 def msg(self):
22 return self._msg
23
24
25def _make_expected_ig_name(filename: pathlib.Path):
26 # Normalize `filename` (e.g. remove `..`) and make it relative to
27 # the root of the source tree.
28 root = pathlib.Path(__file__).parent.parent.resolve(strict=True)
29 filename = filename.absolute().resolve(strict=True).relative_to(root)
30
31 expected_ig_name = re.sub(r"[/.-]", "_", str(filename)).upper()
32 expected_ig_name = re.sub(r"^SRC_", "", expected_ig_name)
33 return "BABELTRACE_" + expected_ig_name
34
35
36def _check_file(filename: pathlib.Path, fix: bool):
37 with open(filename) as f:
38 contents = f.read()
39
40 write_file = False
41
42 # Top part
43 top_re = re.compile(r"^(/\*.+?\*/)\n\n#ifndef (\w+)\n#define (\w+)\n", re.DOTALL)
44 top_m = top_re.match(contents)
45
46 if not top_m:
47 raise _Oops(
48 "Top of the file doesn't have the expected form: block comment, empty line, and then two include guard lines"
49 )
50
51 expected_ig_name = _make_expected_ig_name(filename)
52
53 if fix:
54 contents = top_re.sub(
55 rf"\1\n\n#ifndef {expected_ig_name}\n#define {expected_ig_name}\n",
56 contents,
57 )
58 write_file = True
59 else:
60 if top_m.group(2) != expected_ig_name:
61 raise _Oops(
62 f"In `#ifndef {top_m.group(2)}` include guard line: expecting `#ifndef {expected_ig_name}`"
63 )
64
65 if top_m.group(3) != expected_ig_name:
66 raise _Oops(
67 f"In `#define {top_m.group(3)}` include guard line: expecting `#define {expected_ig_name}`"
68 )
69
70 # Bottom part
71 bottom_re = re.compile(r"\n\n#endif(?: /\* (\w+) \*/)?\n$")
72 bottom_m = bottom_re.search(contents)
73
74 if not bottom_m:
75 raise _Oops("Missing final `#endif` include guard line and trailing empty line")
76
77 if fix:
78 contents = bottom_re.sub(f"\n\n#endif /* {expected_ig_name} */\n", contents)
79 write_file = True
80 else:
81 if bottom_m.group(1) != expected_ig_name:
82 raise _Oops(
83 f"In bottom `#endif` include guard line: expecting `#endif /* {expected_ig_name} */`"
84 )
85
86 if write_file:
87 with open(filename, "w") as f:
88 f.write(contents)
89
90
91def _main():
92 argparser = argparse.ArgumentParser()
93 argparser.add_argument(
94 "-f",
95 "--fix",
96 action="store_true",
97 help="attempt to fix the include guards of FILE",
98 )
99 argparser.add_argument("FILE")
100 args = argparser.parse_args()
101 filename = pathlib.Path(args.FILE)
102
103 try:
104 _check_file(filename, args.fix)
105 except _Oops as exc:
106 print(f"{filename}: {exc}", file=sys.stderr)
107 sys.exit(1)
108
109
110if __name__ == "__main__":
111 _main()
This page took 0.045583 seconds and 4 git commands to generate.