import argparse import os import fileinput import pathlib import re match_expressions = [] valid_extensions = [] root_dir = '' 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: os.system("clang-format -i " + " ".join(files)) 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('Could not parse input filename expression \'{}\': {}'.format(wildcard, str(ex))) quit() for regex in matches: try: match_expressions.append(re.compile(regex, re.IGNORECASE)) except Exception as ex: print('Could not parse input --match expression \'{}\': {}'.format(regex, str(ex))) quit() def _main(): global root_dir 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.") options = parser.parse_args() parse_match_expressions(options.filenames, options.match) root_dir = pathlib.Path(__file__).parent.parent.resolve() 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()