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.
@@ -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)