blob: 7b2fa5b8fa19318307b8e21d5a53c995998330a6 [file] [log] [blame]
#!/usr/bin/python
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
import os.path
import re
import subprocess
import tempfile
import sys
INFO_URL = "https://github.com/apache/mynewt-core/blob/master/CODING_STANDARDS.md"
def get_lines_range(m: re.Match) -> range:
first = int(m.group(1))
if m.group(2) is not None:
last = first + int(m.group(2))
else:
last = first + 1
return range(first, last)
def run_cmd(cmd: str) -> list[str]:
out = subprocess.check_output(cmd, text=True, shell=True)
return out.splitlines()
def check_file(fname: str, commit: str, upstream: str) -> list[str]:
ret = []
diff_lines = set()
for s in run_cmd(f"git diff -U0 {upstream} {commit} -- {fname}"):
m = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", s)
if not m:
continue
diff_lines.update(get_lines_range(m))
with tempfile.NamedTemporaryFile(suffix=os.path.basename(fname)) as tmpf:
lines = subprocess.check_output(f"git show {commit}:{fname}",
shell=True)
tmpf.write(lines)
in_chunk = False
for s in run_cmd(f"uncrustify -q -c uncrustify.cfg -f {tmpf.name} | "
f"diff -u0 -p {tmpf.name} - || true"):
m = re.match(r"^@@ -(\d+)(?:,(\d+))? \+\d+(?:,\d+)? @@", s)
if not m:
if in_chunk:
ret.append(s)
continue
in_chunk = len(diff_lines & set(get_lines_range(m))) != 0
if in_chunk:
ret.append(s)
return ret
def is_ignored(fname: str, ign_dirs: list[str]) -> bool:
if not re.search(r"\.(c|cpp|h|hpp)$", fname):
return True
for d in ign_dirs:
if fname.startswith(d):
return True
return False
def main() -> bool:
if len(sys.argv) > 1:
commit = sys.argv[1]
else:
commit = "HEAD"
if len(sys.argv) > 2:
upstream = sys.argv[2]
else:
upstream = "origin/master"
mb = run_cmd(f"git merge-base {upstream} {commit}")
upstream = mb[0]
cfg_fname = os.path.join(os.path.dirname(__file__), "../.style_ignored_dirs")
with open(cfg_fname, "r") as x:
ign_dirs = [s.strip() for s in x.readlines() if
s.strip() and not s.startswith("#")]
results_ok = []
results_fail = []
results_ign = []
files = run_cmd(f"git diff --diff-filter=AM --name-only {upstream} {commit}")
for fname in files:
if is_ignored(fname, ign_dirs):
results_ign.append(fname)
continue
diff = check_file(fname, commit, upstream)
if len(diff) == 0:
results_ok.append(fname)
else:
results_fail.append((fname, diff))
for fname in results_ign:
print(f"\033[90m[ NA ] {fname}\033[0m")
for fname in results_ok:
print(f"\033[32m[ OK ] {fname}\033[0m")
for fname, diff in results_fail:
print(f"\033[31m[FAIL] {fname}\033[0m")
print("\n".join(diff))
print()
if len(results_fail) > 0:
print(f"\033[31mYour code has style problems, see {INFO_URL} for "
f"details.\033[0m")
return len(results_fail) == 0
if __name__ == "__main__":
if not main():
sys.exit(1)