Skip to content

Commit

Permalink
Add script for verifying more formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
MonsterDruide1 committed Oct 14, 2023
1 parent 4a231bb commit 2f97855
Showing 1 changed file with 170 additions and 0 deletions.
170 changes: 170 additions & 0 deletions tools/check-format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!/usr/bin/env python3

import argparse
from setup import get_build_dir
import subprocess
import os
import re
from common import setup_common as setup

# ------
# CHECKS
# ------

issueFound = False

def FAIL(message, line, path):
print("Offending file:", path)
print("Line:", line)
print(message)
print()
global issueFound
issueFound = True

def CHECK(cond, line, message, path):
if not cond(line):
FAIL(message, line, path)
return True
return False


# Common

def common_no_namespace_qualifiers(c, path):
nest_level = []
for line in c.splitlines():
line = line[0:line.find("//")] if "//" in line else line
if "{" in line and "}" in line:
if CHECK(lambda a:"namespace" not in a, line, "Cannot start and end namespace on the same line!", path): return
if CHECK(lambda a:a.count("{")==a.count("}") or (a.lstrip().startswith("{") and a.endswith("}};")), line, "Unbalanced \"{\" and \"}\" in the same line! (exception: end of brace-initialized array)", path): return
if line.lstrip().startswith("{") and line.endswith("}};"):
del nest_level[-1]
continue
if CHECK(lambda a:a.rfind("namespace") in [-1, 0], line, "\"namespace\" must only be listed at the start of a line!", path): return
if CHECK(lambda a:a.count("{")+a.count("}")<=1, line, "Only one \"{\" and \"}\" is allowed per line!", path): return

if line.startswith("namespace"):
match = re.search("^namespace ([^{\s]*) ?{$", line)
if CHECK(lambda a:match, line, "Unexpected namespace line: should follow format \"namespace xy {\"", path): return
nest_level.append(match.group(1))
# can be "" for "namespace {" and "nn::g3d" for double/triple/... namespaces
continue
elif "{" in line:
nest_level.append("")
elif "}" in line:
del nest_level[-1]

allowed_namespaces = []
for l in nest_level:
if l != "":
if "::" not in l:
allowed_namespaces.append(l)
else:
allowed_namespaces += l.split("::")
matches = re.findall("[\(,\s]([^\(,\s]+::)+[^\(,\s]+", line)
for match in matches:
match = match[0:-2]
# examples: "sead", "al", "nn::g3d"
if CHECK(lambda a:match not in allowed_namespaces, line, match+" should be omitted here!", path): return

if len(nest_level) != 0:
print("ERROR: nest_level not empty at end of the file!")
print("nest_level", nest_level)
exit(1)

def common_newline_eof(c, path):
CHECK(lambda a:a=="", c.splitlines()[-1], "Files should end with a newline!", path)

# Header files

def header_pragma_once(c, path):
CHECK(lambda a:a=="#pragma once", c.splitlines()[0], "Headers must start with \"#pragma once\"!", path)

def header_sorted_visibility(c, path):
visibilities_ordered = ["public:", "protected:", "private:"]
nest_level = []
for line in c.splitlines():
line = line[0:line.find("//")] if "//" in line else line
if "{" in line and "}" in line:
if CHECK(lambda a:a.count("{")==a.count("}") or (a.lstrip().startswith("{") and a.endswith("}};")), line, "Unbalanced \"{\" and \"}\" in the same line! (exception: end of brace-initialized array)", path): return
if line.lstrip().startswith("{") and line.endswith("}};"):
del nest_level[-1]
continue

if CHECK(lambda a:[b for b in visibility_ordered if a in b and a!=b]==[], line, "visibility modificator must be its own line!", path): return
if CHECK(lambda a:a.count("{")+a.count("}")<=1, line, "Only one \"{\" and \"}\" is allowed per line!", path): return

if line in visibilities_ordered:
i = visibilities_ordered.index(line)
if CHECK(lambda a:i>nest_level[-1], line, "Wrong order of visibilities: Must be public, protected, private!", path): return
nest_level[-1] = i
continue
elif "{" in line:
nest_level.append(-1)
elif "}" in line:
del nest_level[-1]

if len(nest_level) != 0:
print("ERROR: nest_level not empty at end of the file!")
print("nest_level", nest_level)
exit(1)



# Source files

def source_include_header(c, path):
rel_path = path.split("src/")[-1] if "src/" in path else path.split("include/")[-1]
header_line = "#include \""+rel_path[0:-3]+"h\""
CHECK(lambda a:a==header_line, c.splitlines()[0], "Source files must start with including respective header in double quotes (here: "+header_line+")!", path)

# -----
# UTILS
# -----

def check_source(c, path):
#common_newline_eof(c, path)
common_no_namespace_qualifiers(c, path)
source_include_header(c, path)

def check_header(c, path):
#common_newline_eof(c, path)
common_no_namespace_qualifiers(c, path)
header_pragma_once(c, path)

def check_file(file_str):
file = open(file_str, mode="r")
content = file.read()
file.close()

if file_str.endswith('.h'):
check_header(content, file_str)
elif file_str.endswith('.cpp'):
check_source(content, file_str)
else:
FAIL("Must only contain .h and .cpp files!", "NOT APPLICABLE", file_str)


project_root = setup.ROOT

def main():
parser = argparse.ArgumentParser(
'check-format.py', description="Verify additional formatting options next to clang-format and clang-tidy")
parser.add_argument('--verbose', action='store_true',
help="Give verbose output")
args = parser.parse_args()

for dir in [project_root/'lib'/'al', project_root/'src']:
for root, _, files in os.walk(dir):
for file in files:
file_path = os.path.join(root, file)
file_str = str(file_path)
check_file(file_str)

if issueFound:
exit(1)
else:
print("No issues found!")

if __name__ == "__main__":
main()

0 comments on commit 2f97855

Please sign in to comment.