洛谷测试点编辑器

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import io
import json
import sys
import tkinter as tk
import zipfile
from tkinter import filedialog

from FlexMenu import FlexMenu


def replace_single_quotes(s):
"""
安全地将Python单引号替换为JSON双引号
处理各种边界情况
"""
result = []
in_string = False
quote_char = None

for i, char in enumerate(s):
if not in_string and char in ("'", '"'):
# 字符串开始
in_string = True
quote_char = char
result.append('"') # JSON总是用双引号
elif in_string and char == quote_char:
# 字符串结束
in_string = False
result.append('"')
elif in_string and char == "\\" and i + 1 < len(s):
# 处理转义字符
result.append(char)
result.append(s[i + 1])
# 跳过下一个字符
continue
else:
result.append(char)

return "".join(result)


def clear_screen():
print(f"\033[A", end="")
print("\033[0J", end="")
sys.stdout.flush()


def edit_test(testid):
global test_in, test_out
while True:
which_file_menu = FlexMenu("请选择要编辑的文件", ["输入文件", "输出文件"])
which_file = which_file_menu.run()
if which_file == -1:
break
if which_file == 0:
while True:
if test_in[testid]:
options = [
(i[:20] + ("..." if len(i) > 20 else ""))
for i in test_in[testid]
]
else:
options = ["(空)"]
options.append("新建一行")

which_line_menu = FlexMenu("请选择要编辑的行", options)
which_line = which_line_menu.run()
if which_line == -1:
break
if which_line == len(options) - 1: # "新建一行"选项
inp = input("请输入新内容: ")
clear_screen()
if inp:
test_in[testid].append(inp)
elif len(test_in[testid]) != 0:
print(which_line)
print(f"当前内容: {test_in[testid][which_line]}")
inp = input("输入新内容 (直接回车删除此行): ")
clear_screen()
if inp:
test_in[testid][which_line] = inp
else:
del test_in[testid][which_line]
else: # 编辑输出文件
while True:
if test_out[testid]:
options = [
(i[:20] + ("..." if len(i) > 20 else ""))
for i in test_out[testid]
]
else:
options = ["(空)"]
options.append("新建一行")

which_line_menu = FlexMenu("请选择要编辑的行", options)
which_line = which_line_menu.run()
if which_line == -1:
break
if which_line == len(options) - 1: # "新建一行"选项
inp = input("请输入新内容: ")
clear_screen()
if inp:
test_out[testid].append(inp)
else:
print(f"当前内容: {test_out[testid][which_line]}")
inp = input("输入新内容 (直接回车删除此行): ")
clear_screen()
if inp:
test_out[testid][which_line] = inp
else:
del test_out[testid][which_line]


test_in = []
test_out = []

while True:
cmd_menu = FlexMenu("测试点编辑器", ["编辑测试点", "生成压缩包", "直接设置"])
cmd = cmd_menu.run()

if cmd == -1:
sys.exit()

if cmd == 2:
which_var_menu = FlexMenu("哪一个变量", ["输入", "输出"])
which_var = which_var_menu.run()

if which_var != -1:
inp = input(
f"请输入 {'输入' if which_var == 0 else '输出'} 变量的新内容 (格式: [[输入1行1, 输入1行2, ...], [输入2行1, 输入2行2, ...], ... ]): "
)
clear_screen()
if inp:
new_inp = []
try:
inp = json.loads(s=replace_single_quotes(inp))
except:
pass
else:
if isinstance(inp, list):
for test in inp:
if not isinstance(test, list):
continue
new_inp.append([])
for line in test:
try:
str(line)
except:
pass
else:
new_inp[-1].append(str(line))

if new_inp:
if which_var == 0:
test_in = new_inp
else:
test_out = new_inp

if cmd == 0: # 编辑测试点
while True:
if test_in:
options = [
f"测试点 #{i+1} (输入行数:{len(test_in[i])}, 输出行数:{len(test_out[i])})"
for i in range(len(test_in))
]
else:
options = ["(暂无测试点)"]
options.append("新建测试点")

which_test_menu = FlexMenu("请选择测试点", options)
which_test = which_test_menu.run()

if which_test == -1:
break

if which_test == len(options) - 1: # "新建测试点"选项
test_in.append([])
test_out.append([])
edit_test(len(test_in) - 1) # 编辑新建的测试点
elif which_test < len(test_in):
edit_test(which_test) # 编辑现有测试点

if cmd == 1: # 生成压缩包
if not test_in:
print("没有测试点可以生成压缩包!")
continue

zip_buffer = io.BytesIO()

with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
for i in range(len(test_in)):
in_content = "\n".join(test_in[i]).encode("utf-8")
out_content = "\n".join(test_out[i]).encode("utf-8")

zip_file.writestr(f"{i+1}.in", in_content)
zip_file.writestr(f"{i+1}.out", out_content)

zip_buffer.seek(0)
zip_data = zip_buffer.getvalue()

print(f"生成的ZIP文件包含 {len(test_in)} 个测试点,大小: {len(zip_data)} 字节")

root = tk.Tk()
root.withdraw()

file_path = filedialog.asksaveasfilename(
title="选择 ZIP 文件保存位置",
defaultextension=".zip",
filetypes=[("ZIP 压缩包文件", "*.zip"), ("所有文件", "*.*")],
)

if file_path:
try:
with open(file_path, "wb") as f:
f.write(zip_data)
print(f"ZIP 文件已保存至: {file_path}")
print("可以直接上传到洛谷测试用例")
except Exception as e:
print(f"保存失败: {e}")
else:
print("保存已取消")

root.destroy()


洛谷测试点编辑器
https://qiufengcute.github.io/Blog/posts/洛谷测试点编辑器/
作者
Qiufeng
发布于
2026年3月2日
许可协议