aboutsummaryrefslogtreecommitdiff
path: root/scripts/formatcode.py
blob: dc13ae1171bbeeead556892d6602ec8543fb71d3 (plain) (blame)
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
import argparse
import os
import fileinput
import pathlib
import re

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 _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

    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()