fastlane-plugin-fastci 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +23 -0
- data/lib/fastlane/plugin/fastci/actions/analyze_swiftlint_action.rb +116 -0
- data/lib/fastlane/plugin/fastci/actions/detect_duplicity_code_action.rb +105 -0
- data/lib/fastlane/plugin/fastci/actions/detect_unused_code_action.rb +102 -0
- data/lib/fastlane/plugin/fastci/actions/detect_unused_image_action.rb +54 -0
- data/lib/fastlane/plugin/fastci/actions/install_certificate_action.rb +40 -0
- data/lib/fastlane/plugin/fastci/actions/install_profile_action.rb +37 -0
- data/lib/fastlane/plugin/fastci/actions/noti_dingding_action.rb +51 -0
- data/lib/fastlane/plugin/fastci/actions/package_action.rb +262 -0
- data/lib/fastlane/plugin/fastci/actions/update_build_number_action.rb +65 -0
- data/lib/fastlane/plugin/fastci/actions/upload_pgy_action.rb +47 -0
- data/lib/fastlane/plugin/fastci/actions/upload_store_action.rb +50 -0
- data/lib/fastlane/plugin/fastci/helper/common_helper.rb +108 -0
- data/lib/fastlane/plugin/fastci/helper/constants.rb +48 -0
- data/lib/fastlane/plugin/fastci/helper/environment.rb +86 -0
- data/lib/fastlane/plugin/fastci/version.rb +5 -0
- data/lib/fastlane/plugin/fastci.rb +16 -0
- data/lib/fastlane/plugin/python/generate_duplicity_code_html.py +270 -0
- data/lib/fastlane/plugin/python/generate_lint_html.py +281 -0
- data/lib/fastlane/plugin/python/generate_unused_code_html.py +328 -0
- data/lib/fastlane/plugin/python/generate_unused_image_html.py +308 -0
- metadata +89 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
from html import escape
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
def parse_xml(file_path):
|
|
8
|
+
tree = ET.parse(file_path)
|
|
9
|
+
root = tree.getroot()
|
|
10
|
+
namespace = {'ns': 'https://pmd-code.org/schema/cpd-report'}
|
|
11
|
+
|
|
12
|
+
duplications = []
|
|
13
|
+
for duplication in root.findall('ns:duplication', namespace):
|
|
14
|
+
code_fragment = duplication.find('ns:codefragment', namespace).text.strip()
|
|
15
|
+
files = []
|
|
16
|
+
for file in duplication.findall('ns:file', namespace):
|
|
17
|
+
path = file.get('path')
|
|
18
|
+
line = file.get('line')
|
|
19
|
+
files.append({'path': path, 'line': line})
|
|
20
|
+
duplications.append({'code': code_fragment, 'files': files})
|
|
21
|
+
|
|
22
|
+
return duplications
|
|
23
|
+
|
|
24
|
+
def generate_html(html_name, duplications, output_file):
|
|
25
|
+
total_duplications = len(duplications)
|
|
26
|
+
|
|
27
|
+
html_content = f"""<!DOCTYPE html>
|
|
28
|
+
<html lang="zh-CN">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="UTF-8">
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
32
|
+
<title>代码重复检测报告</title>
|
|
33
|
+
<style>
|
|
34
|
+
body {{
|
|
35
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
|
36
|
+
margin: 0;
|
|
37
|
+
padding: 20px;
|
|
38
|
+
background-color: #f5f5f5;
|
|
39
|
+
}}
|
|
40
|
+
.container {{
|
|
41
|
+
max-width: 1200px;
|
|
42
|
+
margin: 0 auto;
|
|
43
|
+
background: white;
|
|
44
|
+
border-radius: 8px;
|
|
45
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}}
|
|
48
|
+
.header {{
|
|
49
|
+
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
|
|
50
|
+
color: white;
|
|
51
|
+
padding: 30px;
|
|
52
|
+
text-align: center;
|
|
53
|
+
position: relative;
|
|
54
|
+
}}
|
|
55
|
+
.header h1 {{
|
|
56
|
+
margin: 0;
|
|
57
|
+
font-size: 2em;
|
|
58
|
+
font-weight: 300;
|
|
59
|
+
}}
|
|
60
|
+
.header-timestamp {{
|
|
61
|
+
position: absolute;
|
|
62
|
+
bottom: 15px;
|
|
63
|
+
right: 20px;
|
|
64
|
+
font-size: 0.8em;
|
|
65
|
+
opacity: 0.8;
|
|
66
|
+
}}
|
|
67
|
+
.stats {{
|
|
68
|
+
display: flex;
|
|
69
|
+
justify-content: center;
|
|
70
|
+
gap: 30px;
|
|
71
|
+
margin-top: 20px;
|
|
72
|
+
}}
|
|
73
|
+
.stat-item {{
|
|
74
|
+
text-align: center;
|
|
75
|
+
}}
|
|
76
|
+
.stat-number {{
|
|
77
|
+
font-size: 2em;
|
|
78
|
+
font-weight: bold;
|
|
79
|
+
margin-bottom: 5px;
|
|
80
|
+
}}
|
|
81
|
+
.stat-label {{
|
|
82
|
+
font-size: 0.9em;
|
|
83
|
+
opacity: 0.9;
|
|
84
|
+
}}
|
|
85
|
+
.content {{
|
|
86
|
+
padding: 30px;
|
|
87
|
+
}}
|
|
88
|
+
.block {{
|
|
89
|
+
margin: 20px 0;
|
|
90
|
+
padding: 20px;
|
|
91
|
+
border: 1px solid #e0e0e0;
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
background-color: #fafafa;
|
|
94
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
95
|
+
}}
|
|
96
|
+
.block-header {{
|
|
97
|
+
background: linear-gradient(135deg, #fdcb6e 0%, #e17055 100%);
|
|
98
|
+
color: white;
|
|
99
|
+
padding: 10px 15px;
|
|
100
|
+
margin: -20px -20px 15px -20px;
|
|
101
|
+
border-radius: 8px 8px 0 0;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
}}
|
|
104
|
+
.code {{
|
|
105
|
+
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
background-color: #f8f8f8;
|
|
108
|
+
padding: 15px;
|
|
109
|
+
border-radius: 6px;
|
|
110
|
+
overflow-x: auto;
|
|
111
|
+
border: 1px solid #e0e0e0;
|
|
112
|
+
line-height: 1.5;
|
|
113
|
+
max-height: 400px;
|
|
114
|
+
overflow-y: auto;
|
|
115
|
+
}}
|
|
116
|
+
.file-list {{
|
|
117
|
+
margin-top: 15px;
|
|
118
|
+
padding: 15px;
|
|
119
|
+
background-color: #e8f4fd;
|
|
120
|
+
border-radius: 6px;
|
|
121
|
+
border-left: 4px solid #0984e3;
|
|
122
|
+
}}
|
|
123
|
+
.file-list ul {{
|
|
124
|
+
margin: 5px 0 0 0;
|
|
125
|
+
padding-left: 20px;
|
|
126
|
+
list-style-type: none;
|
|
127
|
+
}}
|
|
128
|
+
.file-list li {{
|
|
129
|
+
margin: 8px 0;
|
|
130
|
+
color: #2d3436;
|
|
131
|
+
padding: 8px 12px;
|
|
132
|
+
background-color: #ffffff;
|
|
133
|
+
border-radius: 4px;
|
|
134
|
+
border: 1px solid #ddd;
|
|
135
|
+
display: flex;
|
|
136
|
+
justify-content: space-between;
|
|
137
|
+
align-items: center;
|
|
138
|
+
flex-wrap: wrap;
|
|
139
|
+
gap: 10px;
|
|
140
|
+
}}
|
|
141
|
+
.file-info {{
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-direction: column;
|
|
144
|
+
flex: 1;
|
|
145
|
+
min-width: 0; /* 允许收缩 */
|
|
146
|
+
}}
|
|
147
|
+
.file-name {{
|
|
148
|
+
font-weight: 600;
|
|
149
|
+
color: #2d3436;
|
|
150
|
+
margin-bottom: 4px;
|
|
151
|
+
}}
|
|
152
|
+
.file-path {{
|
|
153
|
+
font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
|
|
154
|
+
font-size: 0.85em;
|
|
155
|
+
color: #636e72;
|
|
156
|
+
word-break: break-all;
|
|
157
|
+
line-height: 1.4;
|
|
158
|
+
}}
|
|
159
|
+
.line-info {{
|
|
160
|
+
background-color: #74b9ff;
|
|
161
|
+
color: white;
|
|
162
|
+
padding: 4px 8px;
|
|
163
|
+
border-radius: 12px;
|
|
164
|
+
font-size: 0.8em;
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
white-space: nowrap;
|
|
167
|
+
}}
|
|
168
|
+
.no-issues {{
|
|
169
|
+
text-align: center;
|
|
170
|
+
padding: 50px;
|
|
171
|
+
color: #28a745;
|
|
172
|
+
font-size: 1.2em;
|
|
173
|
+
background-color: #d4edda;
|
|
174
|
+
border-radius: 8px;
|
|
175
|
+
border: 1px solid #c3e6cb;
|
|
176
|
+
}}
|
|
177
|
+
|
|
178
|
+
/* 响应式设计 */
|
|
179
|
+
@media (max-width: 768px) {{
|
|
180
|
+
.file-list li {{
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
align-items: flex-start;
|
|
183
|
+
}}
|
|
184
|
+
.line-info {{
|
|
185
|
+
align-self: flex-end;
|
|
186
|
+
}}
|
|
187
|
+
}}
|
|
188
|
+
</style>
|
|
189
|
+
</head>
|
|
190
|
+
<body>
|
|
191
|
+
<div class="container">
|
|
192
|
+
<div class="header">
|
|
193
|
+
<h1>{html_name}</h1>
|
|
194
|
+
<div class="header-timestamp">
|
|
195
|
+
报告生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
196
|
+
</div>
|
|
197
|
+
<div class="stats">
|
|
198
|
+
<div class="stat-item">
|
|
199
|
+
<div class="stat-number">{total_duplications}</div>
|
|
200
|
+
<div class="stat-label">重复代码块</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="content">"""
|
|
205
|
+
|
|
206
|
+
if total_duplications == 0:
|
|
207
|
+
html_content += """
|
|
208
|
+
<div class="no-issues">
|
|
209
|
+
🎉 太棒了!没有发现重复代码
|
|
210
|
+
</div>"""
|
|
211
|
+
else:
|
|
212
|
+
for i, duplication in enumerate(duplications, 1):
|
|
213
|
+
html_content += f"""
|
|
214
|
+
<div class="block">
|
|
215
|
+
<div class="block-header">
|
|
216
|
+
重复代码块 #{i}
|
|
217
|
+
</div>
|
|
218
|
+
<div class="code">
|
|
219
|
+
<pre>{escape(duplication['code'])}</pre>
|
|
220
|
+
</div>
|
|
221
|
+
<div class="file-list">
|
|
222
|
+
<strong>📍 出现位置:</strong>
|
|
223
|
+
<ul>"""
|
|
224
|
+
|
|
225
|
+
for file in duplication['files']:
|
|
226
|
+
filename = os.path.basename(file['path'])
|
|
227
|
+
filepath = file['path']
|
|
228
|
+
line_num = file['line']
|
|
229
|
+
|
|
230
|
+
html_content += f"""
|
|
231
|
+
<li>
|
|
232
|
+
<div class="file-info">
|
|
233
|
+
<div class="file-name">{escape(filename)}</div>
|
|
234
|
+
<div class="file-path">{escape(filepath)}</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="line-info">第 {line_num} 行</div>
|
|
237
|
+
</li>"""
|
|
238
|
+
|
|
239
|
+
html_content += """
|
|
240
|
+
</ul>
|
|
241
|
+
</div>
|
|
242
|
+
</div>"""
|
|
243
|
+
|
|
244
|
+
html_content += """
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</body>
|
|
248
|
+
</html>"""
|
|
249
|
+
|
|
250
|
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
251
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
252
|
+
f.write(html_content)
|
|
253
|
+
|
|
254
|
+
print(f"HTML 报告已生成: {output_file}")
|
|
255
|
+
|
|
256
|
+
if __name__ == '__main__':
|
|
257
|
+
if len(sys.argv) != 4:
|
|
258
|
+
print("用法: python3 generate_duplicity_html.py <html_name> <input_file> <output_file>")
|
|
259
|
+
sys.exit(1)
|
|
260
|
+
|
|
261
|
+
html_name = sys.argv[1]
|
|
262
|
+
input_file = sys.argv[2]
|
|
263
|
+
output_file = sys.argv[3]
|
|
264
|
+
|
|
265
|
+
if not os.path.exists(input_file):
|
|
266
|
+
print(f"输入文件 {input_file} 不存在.")
|
|
267
|
+
sys.exit(1)
|
|
268
|
+
|
|
269
|
+
duplicates_data = parse_xml(input_file)
|
|
270
|
+
generate_html(html_name, duplicates_data, output_file)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def generate_html(html_name, input_file, output_file):
|
|
7
|
+
# 用于存储所有的类型
|
|
8
|
+
all_types = set()
|
|
9
|
+
|
|
10
|
+
# 处理日志文件内容
|
|
11
|
+
rows_html = ""
|
|
12
|
+
total_issues = 0
|
|
13
|
+
|
|
14
|
+
with open(input_file, 'r') as file:
|
|
15
|
+
for line in file:
|
|
16
|
+
# 使用正则表达式解析日志行
|
|
17
|
+
match = re.match(r"^(.*):(\d+):(\d+): (\w+): (.+) \((.+)\)$", line.strip())
|
|
18
|
+
if match:
|
|
19
|
+
file_path = match.group(1)
|
|
20
|
+
line_number = match.group(2)
|
|
21
|
+
column_number = match.group(3)
|
|
22
|
+
level = match.group(4)
|
|
23
|
+
description = match.group(5)
|
|
24
|
+
violation_type = match.group(6)
|
|
25
|
+
|
|
26
|
+
# 收集所有 Type
|
|
27
|
+
all_types.add(violation_type)
|
|
28
|
+
total_issues += 1
|
|
29
|
+
|
|
30
|
+
# 生成表格行
|
|
31
|
+
rows_html += f"""
|
|
32
|
+
<tr>
|
|
33
|
+
<td class="file-name">{os.path.basename(file_path)}</td>
|
|
34
|
+
<td class="line-number">{line_number}</td>
|
|
35
|
+
<td class="level {level.lower()}">{level}</td>
|
|
36
|
+
<td class="description">{description}</td>
|
|
37
|
+
<td class="violation-type">{violation_type}</td>
|
|
38
|
+
</tr>"""
|
|
39
|
+
|
|
40
|
+
# 初始化 HTML 内容
|
|
41
|
+
html_content = f"""<!DOCTYPE html>
|
|
42
|
+
<html lang="zh-CN">
|
|
43
|
+
<head>
|
|
44
|
+
<meta charset="UTF-8">
|
|
45
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
46
|
+
<title>{html_name}</title>
|
|
47
|
+
<style>
|
|
48
|
+
body {{
|
|
49
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: 20px;
|
|
52
|
+
background-color: #f5f5f5;
|
|
53
|
+
}}
|
|
54
|
+
.container {{
|
|
55
|
+
max-width: 1200px;
|
|
56
|
+
margin: 0 auto;
|
|
57
|
+
background: white;
|
|
58
|
+
border-radius: 8px;
|
|
59
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
}}
|
|
62
|
+
.header {{
|
|
63
|
+
background: linear-gradient(135deg, #ff7675 0%, #fd79a8 100%);
|
|
64
|
+
color: white;
|
|
65
|
+
padding: 30px;
|
|
66
|
+
text-align: center;
|
|
67
|
+
position: relative;
|
|
68
|
+
}}
|
|
69
|
+
.header h1 {{
|
|
70
|
+
margin: 0;
|
|
71
|
+
font-size: 2em;
|
|
72
|
+
font-weight: 300;
|
|
73
|
+
}}
|
|
74
|
+
.header-timestamp {{
|
|
75
|
+
position: absolute;
|
|
76
|
+
bottom: 15px;
|
|
77
|
+
right: 20px;
|
|
78
|
+
font-size: 0.8em;
|
|
79
|
+
opacity: 0.8;
|
|
80
|
+
}}
|
|
81
|
+
.stats {{
|
|
82
|
+
display: flex;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
gap: 30px;
|
|
85
|
+
margin-top: 20px;
|
|
86
|
+
}}
|
|
87
|
+
.stat-item {{
|
|
88
|
+
text-align: center;
|
|
89
|
+
}}
|
|
90
|
+
.stat-number {{
|
|
91
|
+
font-size: 2em;
|
|
92
|
+
font-weight: bold;
|
|
93
|
+
margin-bottom: 5px;
|
|
94
|
+
}}
|
|
95
|
+
.stat-label {{
|
|
96
|
+
font-size: 0.9em;
|
|
97
|
+
opacity: 0.9;
|
|
98
|
+
}}
|
|
99
|
+
.content {{
|
|
100
|
+
padding: 30px;
|
|
101
|
+
}}
|
|
102
|
+
table {{
|
|
103
|
+
border-collapse: collapse;
|
|
104
|
+
width: 100%;
|
|
105
|
+
margin-top: 20px;
|
|
106
|
+
table-layout: fixed; /* 固定表格布局 */
|
|
107
|
+
}}
|
|
108
|
+
th, td {{
|
|
109
|
+
border: 1px solid #ddd;
|
|
110
|
+
padding: 12px;
|
|
111
|
+
text-align: left;
|
|
112
|
+
vertical-align: top;
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
word-wrap: break-word;
|
|
115
|
+
}}
|
|
116
|
+
th {{
|
|
117
|
+
background-color: #f8f9fa;
|
|
118
|
+
cursor: pointer;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
color: #495057;
|
|
121
|
+
}}
|
|
122
|
+
/* 设置各列的宽度比例 */
|
|
123
|
+
.file-name, .file-name-header {{
|
|
124
|
+
width: 20%;
|
|
125
|
+
}}
|
|
126
|
+
.line-number, .line-number-header {{
|
|
127
|
+
width: 10%;
|
|
128
|
+
text-align: center;
|
|
129
|
+
}}
|
|
130
|
+
.level, .level-header {{
|
|
131
|
+
width: 10%;
|
|
132
|
+
text-align: center;
|
|
133
|
+
}}
|
|
134
|
+
.description, .description-header {{
|
|
135
|
+
width: 45%;
|
|
136
|
+
}}
|
|
137
|
+
.violation-type, .violation-type-header {{
|
|
138
|
+
width: 15%;
|
|
139
|
+
text-align: center;
|
|
140
|
+
}}
|
|
141
|
+
.filter-select {{
|
|
142
|
+
background: white;
|
|
143
|
+
border: 1px solid #ced4da;
|
|
144
|
+
border-radius: 4px;
|
|
145
|
+
padding: 4px 8px;
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
margin-left: 10px;
|
|
148
|
+
}}
|
|
149
|
+
.warning {{
|
|
150
|
+
color: #ff9800;
|
|
151
|
+
font-weight: bold;
|
|
152
|
+
}}
|
|
153
|
+
.error {{
|
|
154
|
+
color: #f44336;
|
|
155
|
+
font-weight: bold;
|
|
156
|
+
}}
|
|
157
|
+
.hidden {{
|
|
158
|
+
display: none;
|
|
159
|
+
}}
|
|
160
|
+
.no-issues {{
|
|
161
|
+
text-align: center;
|
|
162
|
+
padding: 50px;
|
|
163
|
+
color: #28a745;
|
|
164
|
+
font-size: 1.2em;
|
|
165
|
+
background-color: #d4edda;
|
|
166
|
+
border-radius: 8px;
|
|
167
|
+
border: 1px solid #c3e6cb;
|
|
168
|
+
}}
|
|
169
|
+
tr:nth-child(even) {{
|
|
170
|
+
background-color: #f8f9fa;
|
|
171
|
+
}}
|
|
172
|
+
tr:hover {{
|
|
173
|
+
background-color: #e9ecef;
|
|
174
|
+
}}
|
|
175
|
+
</style>
|
|
176
|
+
<script>
|
|
177
|
+
function filterByType(selectedType) {{
|
|
178
|
+
const rows = document.querySelectorAll("tbody tr");
|
|
179
|
+
rows.forEach(row => {{
|
|
180
|
+
const typeCell = row.querySelector("td:last-child");
|
|
181
|
+
if (selectedType === "All" || typeCell.textContent === selectedType) {{
|
|
182
|
+
row.classList.remove("hidden");
|
|
183
|
+
}} else {{
|
|
184
|
+
row.classList.add("hidden");
|
|
185
|
+
}}
|
|
186
|
+
}});
|
|
187
|
+
|
|
188
|
+
// 更新显示的问题数量
|
|
189
|
+
updateVisibleCount();
|
|
190
|
+
}}
|
|
191
|
+
|
|
192
|
+
function updateVisibleCount() {{
|
|
193
|
+
const visibleRows = document.querySelectorAll("tbody tr:not(.hidden)");
|
|
194
|
+
const countElement = document.getElementById("visible-count");
|
|
195
|
+
if (countElement) {{
|
|
196
|
+
countElement.textContent = visibleRows.length;
|
|
197
|
+
}}
|
|
198
|
+
}}
|
|
199
|
+
</script>
|
|
200
|
+
</head>
|
|
201
|
+
<body>
|
|
202
|
+
<div class="container">
|
|
203
|
+
<div class="header">
|
|
204
|
+
<h1>{html_name}</h1>
|
|
205
|
+
<div class="header-timestamp">
|
|
206
|
+
报告生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
207
|
+
</div>
|
|
208
|
+
<div class="stats">
|
|
209
|
+
<div class="stat-item">
|
|
210
|
+
<div class="stat-number" id="visible-count">{total_issues}</div>
|
|
211
|
+
<div class="stat-label">发现的问题</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
<div class="content">"""
|
|
216
|
+
|
|
217
|
+
if total_issues == 0:
|
|
218
|
+
html_content += """
|
|
219
|
+
<div class="no-issues">
|
|
220
|
+
🎉 太棒了!没有发现代码规范问题
|
|
221
|
+
</div>"""
|
|
222
|
+
else:
|
|
223
|
+
# 添加表格头部和过滤器
|
|
224
|
+
html_content += """
|
|
225
|
+
<table>
|
|
226
|
+
<thead>
|
|
227
|
+
<tr>
|
|
228
|
+
<th class="file-name-header">文件</th>
|
|
229
|
+
<th class="line-number-header">行号</th>
|
|
230
|
+
<th class="level-header">级别</th>
|
|
231
|
+
<th class="description-header">描述</th>
|
|
232
|
+
<th class="violation-type-header">
|
|
233
|
+
类型
|
|
234
|
+
<select class="filter-select" onchange="filterByType(this.value)">
|
|
235
|
+
<option value="All">全部</option>"""
|
|
236
|
+
|
|
237
|
+
# 将所有 Type 添加到下拉框
|
|
238
|
+
for violation_type in sorted(all_types):
|
|
239
|
+
html_content += f'<option value="{violation_type}">{violation_type}</option>'
|
|
240
|
+
|
|
241
|
+
html_content += """
|
|
242
|
+
</select>
|
|
243
|
+
</th>
|
|
244
|
+
</tr>
|
|
245
|
+
</thead>
|
|
246
|
+
<tbody>"""
|
|
247
|
+
|
|
248
|
+
# 添加表格行
|
|
249
|
+
html_content += rows_html
|
|
250
|
+
|
|
251
|
+
html_content += """
|
|
252
|
+
</tbody>
|
|
253
|
+
</table>"""
|
|
254
|
+
|
|
255
|
+
html_content += """
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
</body>
|
|
259
|
+
</html>"""
|
|
260
|
+
|
|
261
|
+
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
|
262
|
+
# 将 HTML 写入文件
|
|
263
|
+
with open(output_file, 'w', encoding='utf-8') as output_file:
|
|
264
|
+
output_file.write(html_content)
|
|
265
|
+
|
|
266
|
+
print(f"HTML 报告已生成: {output_file}")
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
if len(sys.argv) != 4:
|
|
270
|
+
print("用法: python3 generate_lint_html.py <html_name> <input_file> <output_file>")
|
|
271
|
+
sys.exit(1)
|
|
272
|
+
|
|
273
|
+
html_name = sys.argv[1]
|
|
274
|
+
input_file = sys.argv[2]
|
|
275
|
+
output_file = sys.argv[3]
|
|
276
|
+
|
|
277
|
+
if not os.path.exists(input_file):
|
|
278
|
+
print(f"错误: 日志文件 '{input_file}' 不存在.")
|
|
279
|
+
sys.exit(1)
|
|
280
|
+
|
|
281
|
+
generate_html(html_name, input_file, output_file)
|