1#!/usr/bin/env python 2# A tool to parse creates a document outlining how clang formatted the 3# LLVM project is. 4 5import sys 6import os 7import subprocess 8from datetime import datetime 9 10 11def get_git_revision_short_hash(): 12 return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'] 13 ).decode(sys.stdout.encoding).strip() 14 15 16def get_style(count, passed): 17 if passed == count: 18 return ":good:" 19 elif passed != 0: 20 return ":part:" 21 else: 22 return ":none:" 23 24 25TOP_DIR = os.path.join(os.path.dirname(__file__), '../../..') 26CLANG_DIR = os.path.join(os.path.dirname(__file__), '../..') 27DOC_FILE = os.path.join(CLANG_DIR, 'docs/ClangFormattedStatus.rst') 28 29rootdir = TOP_DIR 30 31skipped_dirs = [".git", "test"] 32suffixes = (".cpp", ".h") 33 34rst_prefix = """\ 35.. raw:: html 36 37 <style type="text/css"> 38 .none {{ background-color: #FFCC99 }} 39 .part {{ background-color: #FFFF99 }} 40 .good {{ background-color: #2CCCFF }} 41 .total {{ font-weight: bold; }} 42 </style> 43 44.. role:: none 45.. role:: part 46.. role:: good 47.. role:: total 48 49====================== 50Clang Formatted Status 51====================== 52 53:doc:`ClangFormattedStatus` describes the state of LLVM source 54tree in terms of conformance to :doc:`ClangFormat` as of: {today} (`{sha} <https://github.com/llvm/llvm-project/commit/{sha}>`_). 55 56 57.. list-table:: LLVM Clang-Format Status 58 :widths: 50 25 25 25 25 59 :header-rows: 1\n 60 * - Directory 61 - Total Files 62 - Formatted Files 63 - Unformatted Files 64 - % Complete 65""" 66 67table_row = """\ 68 * - {path} 69 - {style}`{count}` 70 - {style}`{passes}` 71 - {style}`{fails}` 72 - {style2}`{percent}%` 73""" 74 75with open(DOC_FILE, 'wb') as output: 76 sha = get_git_revision_short_hash() 77 today = datetime.now().strftime("%B %d, %Y %H:%M:%S") 78 output.write(bytes(rst_prefix.format(today=today, 79 sha=sha).encode("utf-8"))) 80 81 total_files_count = 0 82 total_files_pass = 0 83 total_files_fail = 0 84 for root, subdirs, files in os.walk(rootdir): 85 for subdir in subdirs: 86 if any(sd == subdir for sd in skipped_dirs): 87 subdirs.remove(subdir) 88 89 path = os.path.relpath(root, TOP_DIR) 90 path = path.replace('\\', '/') 91 92 head, _ = os.path.split(root) 93 while head: 94 head, _ = os.path.split(head) 95 96 file_count = 0 97 file_pass = 0 98 file_fail = 0 99 for filename in files: 100 file_path = os.path.join(root, filename) 101 ext = os.path.splitext(file_path)[-1].lower() 102 if not ext.endswith(suffixes): 103 continue 104 105 file_count += 1 106 107 args = ["clang-format", "-n", file_path] 108 cmd = subprocess.Popen(args, stderr=subprocess.PIPE) 109 stdout, err = cmd.communicate() 110 111 relpath = os.path.relpath(file_path, TOP_DIR) 112 relpath = relpath.replace('\\', '/') 113 if err.decode(sys.stdout.encoding).find(': warning:') > 0: 114 print(relpath, ":", "FAIL") 115 file_fail += 1 116 else: 117 print(relpath, ":", "PASS") 118 file_pass += 1 119 120 total_files_count += file_count 121 total_files_pass += file_pass 122 total_files_fail += file_fail 123 124 if file_count > 0: 125 percent = (int(100.0 * (float(file_pass)/float(file_count)))) 126 style = get_style(file_count, file_pass) 127 output.write(bytes(table_row.format(path=path, 128 count=file_count, 129 passes=file_pass, 130 fails=file_fail, 131 percent=str(percent), style="", 132 style2=style).encode("utf-8"))) 133 output.flush() 134 135 print("----\n") 136 print(path, file_count, file_pass, file_fail, percent) 137 print("----\n") 138 139 total_percent = (float(total_files_pass)/float(total_files_count)) 140 percent_str = str(int(100.0 * total_percent)) 141 output.write(bytes(table_row.format(path="Total", 142 count=total_files_count, 143 passes=total_files_pass, 144 fails=total_files_fail, 145 percent=percent_str, style=":total:", 146 style2=":total:").encode("utf-8"))) 147