xref: /openbsd-src/gnu/llvm/clang/docs/tools/generate_formatted_state.py (revision 12c855180aad702bbcca06e0398d774beeafb155)
1ec727ea7Spatrick#!/usr/bin/env python
2ec727ea7Spatrick# A tool to parse creates a document outlining how clang formatted the
3ec727ea7Spatrick# LLVM project is.
4ec727ea7Spatrick
5ec727ea7Spatrickimport sys
6ec727ea7Spatrickimport os
7ec727ea7Spatrickimport subprocess
8ec727ea7Spatrickfrom datetime import datetime
9ec727ea7Spatrick
10ec727ea7Spatrick
11ec727ea7Spatrickdef get_git_revision_short_hash():
12*12c85518Srobert    """ Get the get SHA in short hash form. """
13ec727ea7Spatrick    return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']
14ec727ea7Spatrick                                   ).decode(sys.stdout.encoding).strip()
15ec727ea7Spatrick
16ec727ea7Spatrick
17ec727ea7Spatrickdef get_style(count, passed):
18*12c85518Srobert    """ Determine if this directory is good based on  the number of clean
19*12c85518Srobert        files vs the number of files in total. """
20ec727ea7Spatrick    if passed == count:
21ec727ea7Spatrick        return ":good:"
22*12c85518Srobert    if passed != 0:
23ec727ea7Spatrick        return ":part:"
24ec727ea7Spatrick    return ":none:"
25ec727ea7Spatrick
26ec727ea7Spatrick
27ec727ea7SpatrickTOP_DIR = os.path.join(os.path.dirname(__file__), '../../..')
28ec727ea7SpatrickCLANG_DIR = os.path.join(os.path.dirname(__file__), '../..')
29ec727ea7SpatrickDOC_FILE = os.path.join(CLANG_DIR, 'docs/ClangFormattedStatus.rst')
30*12c85518SrobertCLEAN_FILE = os.path.join(CLANG_DIR, 'docs/tools/clang-formatted-files.txt')
31ec727ea7Spatrick
32ec727ea7Spatrickrootdir = TOP_DIR
33ec727ea7Spatrick
34ec727ea7Spatrickskipped_dirs = [".git", "test"]
35ec727ea7Spatricksuffixes = (".cpp", ".h")
36ec727ea7Spatrick
37*12c85518SrobertRST_PREFIX = """\
38ec727ea7Spatrick.. raw:: html
39ec727ea7Spatrick
40ec727ea7Spatrick      <style type="text/css">
41ec727ea7Spatrick        .total {{ font-weight: bold; }}
42*12c85518Srobert        .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" }}
43*12c85518Srobert        .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" }}
44*12c85518Srobert        .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" }}
45ec727ea7Spatrick      </style>
46ec727ea7Spatrick
47ec727ea7Spatrick.. role:: none
48ec727ea7Spatrick.. role:: part
49ec727ea7Spatrick.. role:: good
50ec727ea7Spatrick.. role:: total
51ec727ea7Spatrick
52ec727ea7Spatrick======================
53ec727ea7SpatrickClang Formatted Status
54ec727ea7Spatrick======================
55ec727ea7Spatrick
56ec727ea7Spatrick:doc:`ClangFormattedStatus` describes the state of LLVM source
57ec727ea7Spatricktree in terms of conformance to :doc:`ClangFormat` as of: {today} (`{sha} <https://github.com/llvm/llvm-project/commit/{sha}>`_).
58ec727ea7Spatrick
59ec727ea7Spatrick
60ec727ea7Spatrick.. list-table:: LLVM Clang-Format Status
61ec727ea7Spatrick   :widths: 50 25 25 25 25
62ec727ea7Spatrick   :header-rows: 1\n
63ec727ea7Spatrick   * - Directory
64ec727ea7Spatrick     - Total Files
65ec727ea7Spatrick     - Formatted Files
66ec727ea7Spatrick     - Unformatted Files
67ec727ea7Spatrick     - % Complete
68ec727ea7Spatrick"""
69ec727ea7Spatrick
70*12c85518SrobertTABLE_ROW = """\
71ec727ea7Spatrick   * - {path}
72ec727ea7Spatrick     - {style}`{count}`
73ec727ea7Spatrick     - {style}`{passes}`
74ec727ea7Spatrick     - {style}`{fails}`
75ec727ea7Spatrick     - {style2}`{percent}%`
76ec727ea7Spatrick"""
77ec727ea7Spatrick
78ec727ea7SpatrickFNULL = open(os.devnull, 'w')
79ec727ea7Spatrick
80*12c85518Srobert
81ec727ea7Spatrickwith open(DOC_FILE, 'wb') as output:
82*12c85518Srobert    cleanfiles = open(CLEAN_FILE, "wb")
83ec727ea7Spatrick    sha = get_git_revision_short_hash()
84ec727ea7Spatrick    today = datetime.now().strftime("%B %d, %Y %H:%M:%S")
85*12c85518Srobert    output.write(bytes(RST_PREFIX.format(today=today,
86ec727ea7Spatrick                                         sha=sha).encode("utf-8")))
87ec727ea7Spatrick
88ec727ea7Spatrick    total_files_count = 0
89ec727ea7Spatrick    total_files_pass = 0
90ec727ea7Spatrick    total_files_fail = 0
91ec727ea7Spatrick    for root, subdirs, files in os.walk(rootdir):
92ec727ea7Spatrick        for subdir in subdirs:
93ec727ea7Spatrick            if any(sd == subdir for sd in skipped_dirs):
94ec727ea7Spatrick                subdirs.remove(subdir)
95ec727ea7Spatrick            else:
96ec727ea7Spatrick                act_sub_dir = os.path.join(root, subdir)
97ec727ea7Spatrick                # Check the git index to see if the directory contains tracked
98ec727ea7Spatrick                # files. Reditect the output to a null descriptor as we aren't
99ec727ea7Spatrick                # interested in it, just the return code.
100ec727ea7Spatrick                git_check = subprocess.Popen(
101ec727ea7Spatrick                    ["git", "ls-files", "--error-unmatch", act_sub_dir],
102ec727ea7Spatrick                    stdout=FNULL,
103ec727ea7Spatrick                    stderr=FNULL)
104ec727ea7Spatrick                if git_check.wait() != 0:
105ec727ea7Spatrick                    print("Skipping directory: ", act_sub_dir)
106ec727ea7Spatrick                    subdirs.remove(subdir)
107ec727ea7Spatrick
108ec727ea7Spatrick        path = os.path.relpath(root, TOP_DIR)
109ec727ea7Spatrick        path = path.replace('\\', '/')
110ec727ea7Spatrick
111ec727ea7Spatrick        file_count = 0
112ec727ea7Spatrick        file_pass = 0
113ec727ea7Spatrick        file_fail = 0
114ec727ea7Spatrick        for filename in files:
115ec727ea7Spatrick            file_path = os.path.join(root, filename)
116ec727ea7Spatrick            ext = os.path.splitext(file_path)[-1].lower()
117ec727ea7Spatrick            if not ext.endswith(suffixes):
118ec727ea7Spatrick                continue
119ec727ea7Spatrick
120ec727ea7Spatrick            file_count += 1
121ec727ea7Spatrick
122ec727ea7Spatrick            args = ["clang-format", "-n", file_path]
123ec727ea7Spatrick            cmd = subprocess.Popen(args, stderr=subprocess.PIPE)
124ec727ea7Spatrick            stdout, err = cmd.communicate()
125ec727ea7Spatrick
126ec727ea7Spatrick            relpath = os.path.relpath(file_path, TOP_DIR)
127ec727ea7Spatrick            relpath = relpath.replace('\\', '/')
128ec727ea7Spatrick            if err.decode(sys.stdout.encoding).find(': warning:') > 0:
129ec727ea7Spatrick                print(relpath, ":", "FAIL")
130ec727ea7Spatrick                file_fail += 1
131ec727ea7Spatrick            else:
132ec727ea7Spatrick                print(relpath, ":", "PASS")
133ec727ea7Spatrick                file_pass += 1
134*12c85518Srobert                cleanfiles.write(bytes(relpath + "\n"))
135*12c85518Srobert                cleanfiles.flush()
136ec727ea7Spatrick
137ec727ea7Spatrick        total_files_count += file_count
138ec727ea7Spatrick        total_files_pass += file_pass
139ec727ea7Spatrick        total_files_fail += file_fail
140ec727ea7Spatrick
141ec727ea7Spatrick        if file_count > 0:
142ec727ea7Spatrick            percent = (int(100.0 * (float(file_pass)/float(file_count))))
143ec727ea7Spatrick            style = get_style(file_count, file_pass)
144*12c85518Srobert            output.write(bytes(TABLE_ROW.format(path=path,
145ec727ea7Spatrick                                                count=file_count,
146ec727ea7Spatrick                                                passes=file_pass,
147ec727ea7Spatrick                                                fails=file_fail,
148ec727ea7Spatrick                                                percent=str(percent), style="",
149ec727ea7Spatrick                                                style2=style).encode("utf-8")))
150ec727ea7Spatrick            output.flush()
151ec727ea7Spatrick
152ec727ea7Spatrick            print("----\n")
153ec727ea7Spatrick            print(path, file_count, file_pass, file_fail, percent)
154ec727ea7Spatrick            print("----\n")
155ec727ea7Spatrick
156ec727ea7Spatrick    total_percent = (float(total_files_pass)/float(total_files_count))
157ec727ea7Spatrick    percent_str = str(int(100.0 * total_percent))
158*12c85518Srobert    output.write(bytes(TABLE_ROW.format(path="Total",
159ec727ea7Spatrick                                        count=total_files_count,
160ec727ea7Spatrick                                        passes=total_files_pass,
161ec727ea7Spatrick                                        fails=total_files_fail,
162ec727ea7Spatrick                                        percent=percent_str, style=":total:",
163ec727ea7Spatrick                                        style2=":total:").encode("utf-8")))
164