xref: /llvm-project/clang/docs/tools/generate_formatted_state.py (revision c49770c60f26e449379447109f7d915bd8de0384)
16f56a586Spaul_hoad#!/usr/bin/env python
26f56a586Spaul_hoad# A tool to parse creates a document outlining how clang formatted the
36f56a586Spaul_hoad# LLVM project is.
46f56a586Spaul_hoad
56f56a586Spaul_hoadimport sys
66f56a586Spaul_hoadimport os
76f56a586Spaul_hoadimport subprocess
86f56a586Spaul_hoadfrom datetime import datetime
96f56a586Spaul_hoad
106f56a586Spaul_hoad
116f56a586Spaul_hoaddef get_git_revision_short_hash():
125aca8bb9Smydeveloperday    """Get the get SHA in short hash form."""
13dd3c26a0STobias Hieta    return (
14dd3c26a0STobias Hieta        subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
15dd3c26a0STobias Hieta        .decode(sys.stdout.encoding)
16dd3c26a0STobias Hieta        .strip()
17dd3c26a0STobias Hieta    )
186f56a586Spaul_hoad
196f56a586Spaul_hoad
206f56a586Spaul_hoaddef get_style(count, passed):
215aca8bb9Smydeveloperday    """Determine if this directory is good based on  the number of clean
225aca8bb9Smydeveloperday    files vs the number of files in total."""
236f56a586Spaul_hoad    if passed == count:
246f56a586Spaul_hoad        return ":good:"
255aca8bb9Smydeveloperday    if passed != 0:
266f56a586Spaul_hoad        return ":part:"
276f56a586Spaul_hoad    return ":none:"
286f56a586Spaul_hoad
296f56a586Spaul_hoad
30dd3c26a0STobias HietaTOP_DIR = os.path.join(os.path.dirname(__file__), "../../..")
31dd3c26a0STobias HietaCLANG_DIR = os.path.join(os.path.dirname(__file__), "../..")
32dd3c26a0STobias HietaDOC_FILE = os.path.join(CLANG_DIR, "docs/ClangFormattedStatus.rst")
33dd3c26a0STobias HietaCLEAN_FILE = os.path.join(CLANG_DIR, "docs/tools/clang-formatted-files.txt")
346f56a586Spaul_hoad
356f56a586Spaul_hoadrootdir = TOP_DIR
366f56a586Spaul_hoad
376f56a586Spaul_hoadskipped_dirs = [".git", "test"]
386f56a586Spaul_hoadsuffixes = (".cpp", ".h")
396f56a586Spaul_hoad
405aca8bb9SmydeveloperdayRST_PREFIX = """\
416f56a586Spaul_hoad.. raw:: html
426f56a586Spaul_hoad
436f56a586Spaul_hoad      <style type="text/css">
446f56a586Spaul_hoad        .total {{ font-weight: bold; }}
453019898eSmydeveloperday        .none {{ background-color: #FFFF99; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }}
463019898eSmydeveloperday        .part {{ background-color: #FFCC99; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }}
473019898eSmydeveloperday        .good {{ background-color: #2CCCFF; height: 20px; display: inline-block; width: 120px; text-align: center; border-radius: 5px; color: #000000; font-family="Verdana,Geneva,DejaVu Sans,sans-serif" }}
486f56a586Spaul_hoad      </style>
496f56a586Spaul_hoad
506f56a586Spaul_hoad.. role:: none
516f56a586Spaul_hoad.. role:: part
526f56a586Spaul_hoad.. role:: good
536f56a586Spaul_hoad.. role:: total
546f56a586Spaul_hoad
556f56a586Spaul_hoad======================
566f56a586Spaul_hoadClang Formatted Status
576f56a586Spaul_hoad======================
586f56a586Spaul_hoad
596f56a586Spaul_hoad:doc:`ClangFormattedStatus` describes the state of LLVM source
606f56a586Spaul_hoadtree in terms of conformance to :doc:`ClangFormat` as of: {today} (`{sha} <https://github.com/llvm/llvm-project/commit/{sha}>`_).
616f56a586Spaul_hoad
626f56a586Spaul_hoad
636f56a586Spaul_hoad.. list-table:: LLVM Clang-Format Status
646f56a586Spaul_hoad   :widths: 50 25 25 25 25
656f56a586Spaul_hoad   :header-rows: 1\n
666f56a586Spaul_hoad   * - Directory
676f56a586Spaul_hoad     - Total Files
686f56a586Spaul_hoad     - Formatted Files
696f56a586Spaul_hoad     - Unformatted Files
706f56a586Spaul_hoad     - % Complete
716f56a586Spaul_hoad"""
726f56a586Spaul_hoad
735aca8bb9SmydeveloperdayTABLE_ROW = """\
746f56a586Spaul_hoad   * - {path}
756f56a586Spaul_hoad     - {style}`{count}`
766f56a586Spaul_hoad     - {style}`{passes}`
776f56a586Spaul_hoad     - {style}`{fails}`
786f56a586Spaul_hoad     - {style2}`{percent}%`
796f56a586Spaul_hoad"""
806f56a586Spaul_hoad
815aca8bb9Smydeveloperday
82dd3c26a0STobias Hietawith open(DOC_FILE, "wb") as output:
835aca8bb9Smydeveloperday    cleanfiles = open(CLEAN_FILE, "wb")
846f56a586Spaul_hoad    sha = get_git_revision_short_hash()
856f56a586Spaul_hoad    today = datetime.now().strftime("%B %d, %Y %H:%M:%S")
86dd3c26a0STobias Hieta    output.write(bytes(RST_PREFIX.format(today=today, sha=sha).encode("utf-8")))
876f56a586Spaul_hoad
886f56a586Spaul_hoad    total_files_count = 0
896f56a586Spaul_hoad    total_files_pass = 0
906f56a586Spaul_hoad    total_files_fail = 0
916f56a586Spaul_hoad    for root, subdirs, files in os.walk(rootdir):
926f56a586Spaul_hoad        for subdir in subdirs:
936f56a586Spaul_hoad            if any(sd == subdir for sd in skipped_dirs):
946f56a586Spaul_hoad                subdirs.remove(subdir)
95abafb655SNathan James            else:
96abafb655SNathan James                act_sub_dir = os.path.join(root, subdir)
97abafb655SNathan James                # Check the git index to see if the directory contains tracked
98abafb655SNathan James                # files. Reditect the output to a null descriptor as we aren't
99abafb655SNathan James                # interested in it, just the return code.
100abafb655SNathan James                git_check = subprocess.Popen(
101abafb655SNathan James                    ["git", "ls-files", "--error-unmatch", act_sub_dir],
102*c49770c6SNicolas van Kempen                    stdout=subprocess.DEVNULL,
103*c49770c6SNicolas van Kempen                    stderr=subprocess.DEVNULL,
104dd3c26a0STobias Hieta                )
105abafb655SNathan James                if git_check.wait() != 0:
106abafb655SNathan James                    print("Skipping directory: ", act_sub_dir)
107abafb655SNathan James                    subdirs.remove(subdir)
1086f56a586Spaul_hoad
1096f56a586Spaul_hoad        path = os.path.relpath(root, TOP_DIR)
110dd3c26a0STobias Hieta        path = path.replace("\\", "/")
1116f56a586Spaul_hoad
1126f56a586Spaul_hoad        file_count = 0
1136f56a586Spaul_hoad        file_pass = 0
1146f56a586Spaul_hoad        file_fail = 0
1156f56a586Spaul_hoad        for filename in files:
1166f56a586Spaul_hoad            file_path = os.path.join(root, filename)
1176f56a586Spaul_hoad            ext = os.path.splitext(file_path)[-1].lower()
1186f56a586Spaul_hoad            if not ext.endswith(suffixes):
1196f56a586Spaul_hoad                continue
1206f56a586Spaul_hoad
1216f56a586Spaul_hoad            file_count += 1
1226f56a586Spaul_hoad
1236f56a586Spaul_hoad            args = ["clang-format", "-n", file_path]
1246f56a586Spaul_hoad            cmd = subprocess.Popen(args, stderr=subprocess.PIPE)
1256f56a586Spaul_hoad            stdout, err = cmd.communicate()
1266f56a586Spaul_hoad
1276f56a586Spaul_hoad            relpath = os.path.relpath(file_path, TOP_DIR)
128dd3c26a0STobias Hieta            relpath = relpath.replace("\\", "/")
129dd3c26a0STobias Hieta            if err.decode(sys.stdout.encoding).find(": warning:") > 0:
1306f56a586Spaul_hoad                print(relpath, ":", "FAIL")
1316f56a586Spaul_hoad                file_fail += 1
1326f56a586Spaul_hoad            else:
1336f56a586Spaul_hoad                print(relpath, ":", "PASS")
1346f56a586Spaul_hoad                file_pass += 1
1355aca8bb9Smydeveloperday                cleanfiles.write(bytes(relpath + "\n"))
1365aca8bb9Smydeveloperday                cleanfiles.flush()
1376f56a586Spaul_hoad
1386f56a586Spaul_hoad        total_files_count += file_count
1396f56a586Spaul_hoad        total_files_pass += file_pass
1406f56a586Spaul_hoad        total_files_fail += file_fail
1416f56a586Spaul_hoad
1426f56a586Spaul_hoad        if file_count > 0:
143dd3c26a0STobias Hieta            percent = int(100.0 * (float(file_pass) / float(file_count)))
1446f56a586Spaul_hoad            style = get_style(file_count, file_pass)
145dd3c26a0STobias Hieta            output.write(
146dd3c26a0STobias Hieta                bytes(
147dd3c26a0STobias Hieta                    TABLE_ROW.format(
148dd3c26a0STobias Hieta                        path=path,
1496f56a586Spaul_hoad                        count=file_count,
1506f56a586Spaul_hoad                        passes=file_pass,
1516f56a586Spaul_hoad                        fails=file_fail,
152dd3c26a0STobias Hieta                        percent=str(percent),
153dd3c26a0STobias Hieta                        style="",
154dd3c26a0STobias Hieta                        style2=style,
155dd3c26a0STobias Hieta                    ).encode("utf-8")
156dd3c26a0STobias Hieta                )
157dd3c26a0STobias Hieta            )
1586f56a586Spaul_hoad            output.flush()
1596f56a586Spaul_hoad
1606f56a586Spaul_hoad            print("----\n")
1616f56a586Spaul_hoad            print(path, file_count, file_pass, file_fail, percent)
1626f56a586Spaul_hoad            print("----\n")
1636f56a586Spaul_hoad
164dd3c26a0STobias Hieta    total_percent = float(total_files_pass) / float(total_files_count)
1656f56a586Spaul_hoad    percent_str = str(int(100.0 * total_percent))
166dd3c26a0STobias Hieta    output.write(
167dd3c26a0STobias Hieta        bytes(
168dd3c26a0STobias Hieta            TABLE_ROW.format(
169dd3c26a0STobias Hieta                path="Total",
1706f56a586Spaul_hoad                count=total_files_count,
1716f56a586Spaul_hoad                passes=total_files_pass,
1726f56a586Spaul_hoad                fails=total_files_fail,
173dd3c26a0STobias Hieta                percent=percent_str,
174dd3c26a0STobias Hieta                style=":total:",
175dd3c26a0STobias Hieta                style2=":total:",
176dd3c26a0STobias Hieta            ).encode("utf-8")
177dd3c26a0STobias Hieta        )
178dd3c26a0STobias Hieta    )
179