1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
import argparse
import fileinput
import os
import pathlib
import re
import subprocess
match_expressions = []
valid_extensions = []
root_dir = ''
use_batching = True
def is_header_missing(f):
with open(f) as reader:
lines = reader.read().lstrip().splitlines()
if len(lines) > 0: return not lines[0].startswith("// ")
return True
def add_headers(files, header):
for line in fileinput.input(files, inplace=True):
if fileinput.isfirstline():
[ print(h) for h in header.splitlines() ]
print(line, end="")
def scan_tree(root):
files = []
header_files = []
with os.scandir(root) as dirs:
for entry in dirs:
if entry.is_dir():
scan_tree(os.path.join(root, entry.name))
continue
full_path = os.path.join(root, entry.name)
relative_root_path = os.path.relpath(full_path, start=root_dir)
if is_matching_filename(relative_root_path):
print("... formatting: {}".format(relative_root_path))
files.append(full_path)
if is_header_missing(full_path):
header_files.append(full_path)
args = ""
if files:
if use_batching:
os.system("clang-format -i " + " ".join(files))
else:
for file in files:
os.system("clang-format -i " + file)
if header_files:
add_headers(header_files, "// Copyright Epic Games, Inc. All Rights Reserved.\n\n")
def scan_zen(root):
with os.scandir(root) as dirs:
for entry in dirs:
if entry.is_dir() and entry.name.startswith("zen"):
scan_tree(os.path.join(root, entry.name))
def is_matching_filename(relative_root_path):
global match_expressions
global root_dir
global valid_extensions
if os.path.splitext(relative_root_path)[1].lower() not in valid_extensions:
return False
if not match_expressions:
return True
relative_root_path = relative_root_path.replace('\\', '/')
for regex in match_expressions:
if regex.fullmatch(relative_root_path):
return True
return False
def parse_match_expressions(wildcards, matches):
global match_expressions
global valid_extensions
valid_extensions = ['.cpp', '.h']
for wildcard in wildcards:
regex = wildcard.replace('*', '%FORMAT_STAR%').replace('\\', '/')
regex = re.escape(regex)
regex = '.*' + regex.replace('%FORMAT_STAR%', '.*') + '.*'
try:
match_expressions.append(re.compile(regex, re.IGNORECASE))
except Exception as ex:
print(f'Could not parse input filename expression \'{wildcard}\': {str(ex)}')
quit()
for regex in matches:
try:
match_expressions.append(re.compile(regex, re.IGNORECASE))
except Exception as ex:
print(f'Could not parse input --match expression \'{regex}\': {str(ex)}')
quit()
def validate_clang_format():
vstring = subprocess.check_output("clang-format --version", shell=True).decode().rstrip()
match = re.search(r'(\d+)\.(\d+)(\.(\d+))?$', vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch) = match.group(1, 2, 4)
if int(major) < 13:
if int(minor) == 0:
if int(patch) < 1:
raise ValueError(f'invalid clang-format version -- we require at least v12.0.1')
def _main():
global root_dir, use_batching
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help="Match text for filenames. If fullpath contains text it is a match, " +\
"* is a wildcard. Directory separators are matched by either / or \\. Case insensitive.")
parser.add_argument('--match', action='append', default=[], help="Match regular expression for filenames. " +\
"Relative path from the root zen directory must be a complete match. Directory separators are matched only by /. Case insensitive.")
parser.add_argument('--batch', dest='use_batching', action='store_true', help="Enable batching calls to clang-format.")
parser.add_argument('--no-batch', dest='use_batching', action='store_false', help="Disable batching calls to clang-format.")
parser.set_defaults(use_batching=True)
options = parser.parse_args()
parse_match_expressions(options.filenames, options.match)
root_dir = pathlib.Path(__file__).parent.parent.resolve()
use_batching = options.use_batching
validate_clang_format()
while True:
if (os.path.isfile(".clang-format")):
scan_zen(".")
quit()
else:
cwd = os.getcwd()
if os.path.dirname(cwd) == cwd:
quit()
os.chdir("..")
if __name__ == '__main__':
_main()
|