AtCoderStudyBooster 0.1.1__py3-none-any.whl → 0.3__py3-none-any.whl
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.
- atcdr/download.py +117 -94
- atcdr/generate.py +76 -23
- atcdr/main.py +5 -0
- atcdr/markdown.py +39 -0
- atcdr/open.py +27 -20
- atcdr/test.py +304 -142
- atcdr/util/execute.py +63 -0
- atcdr/util/filetype.py +105 -0
- atcdr/util/problem.py +36 -32
- {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/METADATA +3 -3
- atcoderstudybooster-0.3.dist-info/RECORD +17 -0
- atcdr/util/filename.py +0 -137
- atcoderstudybooster-0.1.1.dist-info/RECORD +0 -15
- {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/WHEEL +0 -0
- {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/entry_points.txt +0 -0
atcdr/test.py
CHANGED
@@ -2,17 +2,28 @@ import os
|
|
2
2
|
import subprocess
|
3
3
|
import tempfile
|
4
4
|
import time
|
5
|
-
from dataclasses import dataclass
|
5
|
+
from dataclasses import dataclass, field
|
6
6
|
from enum import Enum
|
7
|
-
from typing import
|
7
|
+
from typing import Dict, Generator, List, Tuple, Union, Optional
|
8
8
|
|
9
|
-
import colorama
|
10
9
|
from bs4 import BeautifulSoup as bs
|
11
|
-
from
|
12
|
-
|
13
|
-
from
|
14
|
-
|
15
|
-
|
10
|
+
from rich.console import Console, Group, RenderableType
|
11
|
+
from rich.markup import escape
|
12
|
+
from rich.panel import Panel
|
13
|
+
from rich.rule import Rule
|
14
|
+
from rich.style import Style
|
15
|
+
from rich.syntax import Syntax
|
16
|
+
from rich.table import Table
|
17
|
+
from rich.text import Text
|
18
|
+
|
19
|
+
from atcdr.util.execute import execute_files
|
20
|
+
from atcdr.util.filetype import (
|
21
|
+
COMPILED_LANGUAGES,
|
22
|
+
INTERPRETED_LANGUAGES,
|
23
|
+
Lang,
|
24
|
+
detect_language,
|
25
|
+
lang2str,
|
26
|
+
)
|
16
27
|
|
17
28
|
|
18
29
|
@dataclass
|
@@ -28,12 +39,13 @@ class LabeledTestCase:
|
|
28
39
|
|
29
40
|
|
30
41
|
class ResultStatus(Enum):
|
31
|
-
|
32
|
-
|
42
|
+
AC = 'Accepted'
|
43
|
+
WA = 'Wrong Answer'
|
33
44
|
TLE = 'Time Limit Exceeded'
|
45
|
+
MLE = 'Memory Limit Exceeded'
|
34
46
|
RE = 'Runtime Error'
|
35
|
-
|
36
|
-
|
47
|
+
CE = 'Compilation Error'
|
48
|
+
WJ = 'Juding ...'
|
37
49
|
|
38
50
|
|
39
51
|
@dataclass
|
@@ -48,10 +60,20 @@ class TestCaseResult:
|
|
48
60
|
class LabeledTestCaseResult:
|
49
61
|
label: str
|
50
62
|
testcase: TestCase
|
51
|
-
# TODO : 実はラベル自体を使わない方がいいかもしれない.ラベルという概念が削除してプリントするときに適当にTest1, Test2と適当に名前をつけてもいいかも.
|
52
63
|
result: TestCaseResult
|
53
64
|
|
54
65
|
|
66
|
+
@dataclass
|
67
|
+
class TestInformation:
|
68
|
+
lang: Lang
|
69
|
+
sourcename: str
|
70
|
+
case_number: int
|
71
|
+
result_summary: ResultStatus = ResultStatus.WJ
|
72
|
+
resultlist: List[LabeledTestCaseResult] = field(default_factory=list) # 修正
|
73
|
+
compiler_message: str = ''
|
74
|
+
compile_time : Optional[int] = None
|
75
|
+
|
76
|
+
|
55
77
|
def create_testcases_from_html(html: str) -> List[LabeledTestCase]:
|
56
78
|
soup = bs(html, 'html.parser')
|
57
79
|
test_cases = []
|
@@ -84,173 +106,312 @@ def create_testcases_from_html(html: str) -> List[LabeledTestCase]:
|
|
84
106
|
|
85
107
|
|
86
108
|
def run_code(cmd: list, case: TestCase) -> TestCaseResult:
|
109
|
+
start_time = time.time()
|
87
110
|
try:
|
88
|
-
start_time = time.time()
|
89
111
|
proc = subprocess.run(
|
90
112
|
cmd, input=case.input, text=True, capture_output=True, timeout=4
|
91
113
|
)
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
if
|
97
|
-
|
98
|
-
|
99
|
-
|
114
|
+
executed_time = int((time.time() - start_time) * 1000)
|
115
|
+
except subprocess.TimeoutExpired as e_proc:
|
116
|
+
executed_time = int((time.time() - start_time) * 1000)
|
117
|
+
stdout_text = e_proc.stdout.decode('utf-8') if e_proc.stdout is not None else ''
|
118
|
+
stderr_text = e_proc.stderr.decode('utf-8') if e_proc.stderr is not None else ''
|
119
|
+
text = stdout_text + '\n' + stderr_text
|
120
|
+
return TestCaseResult(
|
121
|
+
output=text, executed_time=executed_time, passed=ResultStatus.TLE
|
122
|
+
)
|
100
123
|
|
101
|
-
|
102
|
-
|
124
|
+
# プロセスの終了コードを確認し、異常終了ならREを返す
|
125
|
+
if proc.returncode != 0:
|
126
|
+
return TestCaseResult(
|
127
|
+
output=proc.stdout + '\n' + proc.stderr,
|
128
|
+
executed_time=executed_time,
|
129
|
+
passed=ResultStatus.RE,
|
130
|
+
)
|
103
131
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
executed_time=execution_time,
|
108
|
-
passed=ResultStatus.WA,
|
109
|
-
)
|
132
|
+
# 実際の出力と期待される出力を比較
|
133
|
+
actual_output = proc.stdout.strip()
|
134
|
+
expected_output = case.output.strip()
|
110
135
|
|
136
|
+
if actual_output != expected_output:
|
111
137
|
return TestCaseResult(
|
112
|
-
output=actual_output,
|
138
|
+
output=actual_output,
|
139
|
+
executed_time=executed_time,
|
140
|
+
passed=ResultStatus.WA,
|
113
141
|
)
|
114
|
-
|
142
|
+
else:
|
115
143
|
return TestCaseResult(
|
116
|
-
output=
|
144
|
+
output=actual_output, executed_time=executed_time, passed=ResultStatus.AC
|
117
145
|
)
|
118
|
-
except Exception as e:
|
119
|
-
return TestCaseResult(output=str(e), executed_time=None, passed=ResultStatus.RE)
|
120
146
|
|
121
147
|
|
122
|
-
|
123
|
-
|
124
|
-
|
148
|
+
LANGUAGE_RUN_COMMANDS: Dict[Lang, list] = {
|
149
|
+
Lang.PYTHON: ['python3', '{source_path}'],
|
150
|
+
Lang.JAVASCRIPT: ['node', '{source_path}'],
|
151
|
+
Lang.C: ['{exec_path}'],
|
152
|
+
Lang.CPP: ['{exec_path}'],
|
153
|
+
Lang.RUST: ['{exec_path}'],
|
154
|
+
Lang.JAVA: ['java', '{exec_path}'],
|
155
|
+
}
|
125
156
|
|
126
|
-
|
127
|
-
|
157
|
+
LANGUAGE_COMPILE_COMMANDS: Dict[Lang, list] = {
|
158
|
+
Lang.C: ['gcc', '{source_path}', '-o', '{exec_path}'],
|
159
|
+
Lang.CPP: ['g++', '{source_path}', '-o', '{exec_path}'],
|
160
|
+
Lang.RUST: ['rustc', '{source_path}', '-o', '{exec_path}'],
|
161
|
+
Lang.JAVA: ['javac', '{source_path}'],
|
162
|
+
}
|
128
163
|
|
129
164
|
|
130
|
-
def
|
165
|
+
def run_compile(path: str, lang: Lang) -> Tuple[str, subprocess.CompletedProcess]:
|
131
166
|
with tempfile.NamedTemporaryFile(delete=True) as tmp:
|
132
167
|
exec_path = tmp.name
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
if compile_result.stderr:
|
141
|
-
print(f'コンパイラーからのメッセージ\n{compile_result.stderr}')
|
142
|
-
return run_code([exec_path], case)
|
168
|
+
cmd = [
|
169
|
+
arg.format(source_path=path, exec_path=exec_path)
|
170
|
+
for arg in LANGUAGE_COMPILE_COMMANDS[lang]
|
171
|
+
]
|
172
|
+
start_time = time.time()
|
173
|
+
compile_result = subprocess.run(cmd, capture_output=True, text=True)
|
174
|
+
compile_time = int((time.time() - start_time) * 1000)
|
143
175
|
|
176
|
+
return exec_path, compile_result, compile_time
|
144
177
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
178
|
+
|
179
|
+
def judge_code_from(
|
180
|
+
lcases: List[LabeledTestCase], path: str
|
181
|
+
) -> Generator[
|
182
|
+
Union[LabeledTestCaseResult, TestInformation], # type: ignore
|
183
|
+
None,
|
184
|
+
None,
|
185
|
+
]:
|
186
|
+
lang = detect_language(path)
|
187
|
+
if lang in COMPILED_LANGUAGES:
|
188
|
+
exe_path, compile_result, compile_time = run_compile(path, lang)
|
151
189
|
if compile_result.returncode != 0:
|
152
|
-
|
153
|
-
|
190
|
+
yield TestInformation(
|
191
|
+
lang=lang,
|
192
|
+
sourcename=path,
|
193
|
+
case_number=len(lcases),
|
194
|
+
result_summary=ResultStatus.CE,
|
195
|
+
compiler_message=compile_result.stderr,
|
196
|
+
)
|
197
|
+
return
|
198
|
+
else:
|
199
|
+
yield TestInformation(
|
200
|
+
lang=lang,
|
201
|
+
sourcename=path,
|
202
|
+
case_number=len(lcases),
|
203
|
+
compiler_message=compile_result.stderr,
|
204
|
+
compile_time=compile_time,
|
154
205
|
)
|
155
|
-
if compile_result.stderr:
|
156
|
-
print(f'コンパイラーからのメッセージ\n{compile_result.stderr}')
|
157
|
-
return run_code([exec_path], case)
|
158
206
|
|
207
|
+
cmd = [
|
208
|
+
arg.format(exec_path=exe_path) for arg in LANGUAGE_RUN_COMMANDS[lang]
|
209
|
+
]
|
159
210
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
['rustc', path, '-o', exec_path], capture_output=True, text=True
|
165
|
-
)
|
166
|
-
if compile_result.returncode != 0:
|
167
|
-
return TestCaseResult(
|
168
|
-
output=compile_result.stderr, executed_time=None, passed=ResultStatus.CE
|
169
|
-
)
|
170
|
-
if compile_result.stderr:
|
171
|
-
print(f'コンパイラーからのメッセージ\n{compile_result.stderr}')
|
172
|
-
return run_code([exec_path], case)
|
211
|
+
for lcase in lcases:
|
212
|
+
yield LabeledTestCaseResult(
|
213
|
+
lcase.label, lcase.case, run_code(cmd, lcase.case)
|
214
|
+
)
|
173
215
|
|
216
|
+
if os.path.exists(exe_path):
|
217
|
+
os.remove(exe_path)
|
174
218
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
219
|
+
elif lang in INTERPRETED_LANGUAGES:
|
220
|
+
yield TestInformation(
|
221
|
+
lang=lang,
|
222
|
+
sourcename=path,
|
223
|
+
case_number=len(lcases),
|
180
224
|
)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
225
|
+
cmd = [arg.format(source_path=path) for arg in LANGUAGE_RUN_COMMANDS[lang]]
|
226
|
+
for lcase in lcases:
|
227
|
+
yield LabeledTestCaseResult(
|
228
|
+
lcase.label, lcase.case, run_code(cmd, lcase.case)
|
229
|
+
)
|
230
|
+
else:
|
231
|
+
raise ValueError('適切な言語が見つかりませんでした.')
|
232
|
+
|
233
|
+
|
234
|
+
COLOR_MAP = {
|
235
|
+
ResultStatus.AC: 'green',
|
236
|
+
ResultStatus.WA: 'red',
|
237
|
+
ResultStatus.TLE: 'yellow',
|
238
|
+
ResultStatus.MLE: 'yellow',
|
239
|
+
ResultStatus.RE: 'yellow',
|
240
|
+
ResultStatus.CE: 'yellow',
|
241
|
+
ResultStatus.WJ: 'grey',
|
197
242
|
}
|
198
243
|
|
244
|
+
STATUS_TEXT_MAP = {
|
245
|
+
ResultStatus.AC: Text.assemble(
|
246
|
+
('\u2713 ', 'green'),
|
247
|
+
(
|
248
|
+
f'{ResultStatus.AC.value}',
|
249
|
+
Style(bgcolor=COLOR_MAP[ResultStatus.AC], bold=True),
|
250
|
+
),
|
251
|
+
),
|
252
|
+
ResultStatus.WA: Text(
|
253
|
+
f'\u00d7 {ResultStatus.WA.value}', style=COLOR_MAP[ResultStatus.WA]
|
254
|
+
),
|
255
|
+
ResultStatus.TLE: Text(
|
256
|
+
f'\u00d7 {ResultStatus.TLE.value}', style=COLOR_MAP[ResultStatus.TLE]
|
257
|
+
),
|
258
|
+
ResultStatus.MLE: Text(
|
259
|
+
f'\u00d7 {ResultStatus.MLE.value}', style=COLOR_MAP[ResultStatus.MLE]
|
260
|
+
),
|
261
|
+
ResultStatus.RE: Text(
|
262
|
+
f'\u00d7 {ResultStatus.RE.value}', style=COLOR_MAP[ResultStatus.RE]
|
263
|
+
),
|
264
|
+
ResultStatus.CE: Text(
|
265
|
+
f'\u00d7 {ResultStatus.CE.value}', style=COLOR_MAP[ResultStatus.CE]
|
266
|
+
),
|
267
|
+
ResultStatus.WJ: Text(
|
268
|
+
f'\u23f3 {ResultStatus.WJ.value}', style=COLOR_MAP[ResultStatus.WJ]
|
269
|
+
),
|
270
|
+
}
|
199
271
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
272
|
+
|
273
|
+
def create_renderable_test_info(test_info: TestInformation) -> RenderableType:
|
274
|
+
components = []
|
275
|
+
|
276
|
+
success_count = sum(
|
277
|
+
1 for result in test_info.resultlist if result.result.passed == ResultStatus.AC
|
278
|
+
)
|
279
|
+
total_count = test_info.case_number
|
280
|
+
|
281
|
+
# 結果に応じたスタイル付きのテキストを取得
|
282
|
+
status_text = STATUS_TEXT_MAP[test_info.result_summary]
|
283
|
+
|
284
|
+
# ヘッダーのテキストを構築
|
285
|
+
header_text = Text.assemble(
|
286
|
+
Text.from_markup(f'[cyan]{test_info.sourcename}[/]のテスト [\n]'),
|
287
|
+
Text.from_markup(f'[gray]コンパイルにかかった時間: [italic]{test_info.compile_time} ms[/][/][\n]') if test_info.compile_time else Text(''),
|
288
|
+
status_text,
|
289
|
+
Text.from_markup(
|
290
|
+
f' [{COLOR_MAP[test_info.result_summary]} bold]{success_count}[/] / [white bold]{total_count}[/]'
|
291
|
+
),
|
204
292
|
)
|
205
|
-
# lang が None でない場合のみ get を呼び出す
|
206
|
-
if lang is not None:
|
207
|
-
return LANGUAGE_RUNNERS.get(lang)
|
208
|
-
return None
|
209
293
|
|
294
|
+
components.append(Panel(header_text, expand=False))
|
210
295
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
296
|
+
if test_info.compiler_message:
|
297
|
+
rule = Rule(
|
298
|
+
title='コンパイラーのメッセージ',
|
299
|
+
style=COLOR_MAP[ResultStatus.CE],
|
300
|
+
)
|
301
|
+
components.append(rule)
|
302
|
+
error_message = Syntax(
|
303
|
+
test_info.compiler_message, lang2str(test_info.lang), line_numbers=False
|
304
|
+
)
|
305
|
+
components.append(error_message)
|
306
|
+
|
307
|
+
return Group(*components)
|
308
|
+
|
309
|
+
|
310
|
+
def update_test_info(
|
311
|
+
test_info: TestInformation, test_result: LabeledTestCaseResult
|
312
|
+
) -> None:
|
313
|
+
test_info.resultlist.append(test_result)
|
314
|
+
|
315
|
+
priority_order = [
|
316
|
+
ResultStatus.RE,
|
317
|
+
ResultStatus.WA,
|
318
|
+
ResultStatus.TLE,
|
319
|
+
ResultStatus.MLE,
|
320
|
+
ResultStatus.WJ,
|
321
|
+
ResultStatus.AC,
|
221
322
|
]
|
222
323
|
|
324
|
+
# 現在の結果の中で最も高い優先順位のステータスを見つける
|
325
|
+
highest_priority_status = (
|
326
|
+
test_info.result_summary
|
327
|
+
) # デフォルトはWJまたは現在のサマリー
|
328
|
+
for result in test_info.resultlist:
|
329
|
+
status = result.result.passed
|
330
|
+
if priority_order.index(status) < priority_order.index(highest_priority_status):
|
331
|
+
highest_priority_status = status
|
332
|
+
|
333
|
+
# 特殊ケース: すべてのテストケースがACである場合(途中でも)
|
334
|
+
if all(result.result.passed == ResultStatus.AC for result in test_info.resultlist):
|
335
|
+
test_info.result_summary = ResultStatus.AC
|
336
|
+
else:
|
337
|
+
test_info.result_summary = highest_priority_status
|
338
|
+
|
339
|
+
|
340
|
+
def create_renderable_test_result(
|
341
|
+
i: int,
|
342
|
+
test_result: LabeledTestCaseResult,
|
343
|
+
) -> RenderableType:
|
344
|
+
rule = Rule(
|
345
|
+
title=f'No.{i+1} {test_result.label}',
|
346
|
+
style=COLOR_MAP[test_result.result.passed],
|
347
|
+
)
|
223
348
|
|
224
|
-
|
225
|
-
|
349
|
+
# 以下の部分は if-else ブロックの外に移動
|
350
|
+
status_header = Text.assemble(
|
351
|
+
'ステータス ',
|
352
|
+
STATUS_TEXT_MAP[test_result.result.passed], # status_text をここに追加
|
353
|
+
)
|
226
354
|
|
355
|
+
execution_time_text = None
|
356
|
+
if test_result.result.executed_time is not None:
|
357
|
+
execution_time_text = Text.from_markup(
|
358
|
+
f'実行時間 [cyan]{test_result.result.executed_time}[/cyan] ms'
|
359
|
+
)
|
227
360
|
|
228
|
-
|
229
|
-
|
230
|
-
result = lresult.result
|
231
|
-
testcase = lresult.testcase
|
361
|
+
table = Table(show_header=True, header_style='bold')
|
362
|
+
table.add_column('入力', style='cyan', min_width=10)
|
232
363
|
|
233
|
-
if result.passed
|
234
|
-
|
235
|
-
|
364
|
+
if test_result.result.passed != ResultStatus.AC:
|
365
|
+
table.add_column(
|
366
|
+
'出力', style=COLOR_MAP[test_result.result.passed], min_width=10
|
367
|
+
)
|
368
|
+
table.add_column('正解の出力', style=COLOR_MAP[ResultStatus.AC], min_width=10)
|
369
|
+
table.add_row(
|
370
|
+
escape(test_result.testcase.input),
|
371
|
+
escape(test_result.result.output),
|
372
|
+
escape(test_result.testcase.output),
|
236
373
|
)
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
374
|
+
else:
|
375
|
+
table.add_column(
|
376
|
+
'出力', style=COLOR_MAP[test_result.result.passed], min_width=10
|
377
|
+
)
|
378
|
+
table.add_row(
|
379
|
+
escape(test_result.testcase.input), escape(test_result.result.output)
|
241
380
|
)
|
242
|
-
elif result.passed == ResultStatus.RE:
|
243
|
-
output += Fore.YELLOW + f'[RE] Runtime Error\n Output:\n{result.output}'
|
244
|
-
elif result.passed == ResultStatus.TLE:
|
245
|
-
output += Fore.YELLOW + '[TLE] Time Limit Exceeded\n'
|
246
|
-
elif result.passed == ResultStatus.CE:
|
247
|
-
output += Fore.YELLOW + f'[CE] Compile Error\n Output:\n{result.output}'
|
248
|
-
elif result.passed == ResultStatus.MLE:
|
249
|
-
output += Fore.YELLOW + '[ME] Memory Limit Exceeded\n'
|
250
381
|
|
251
|
-
|
382
|
+
components = [
|
383
|
+
rule,
|
384
|
+
status_header,
|
385
|
+
execution_time_text if execution_time_text else '',
|
386
|
+
table,
|
387
|
+
]
|
252
388
|
|
253
|
-
return
|
389
|
+
return Group(*components)
|
390
|
+
|
391
|
+
|
392
|
+
def render_results(
|
393
|
+
results: Generator[Union[LabeledTestCaseResult, TestInformation], None, None],
|
394
|
+
) -> None:
|
395
|
+
console = Console()
|
396
|
+
|
397
|
+
# 最初の結果は TestInformation として取得
|
398
|
+
first_result = next(results)
|
399
|
+
if not isinstance(first_result, TestInformation):
|
400
|
+
raise ValueError('最初のジェネレーターの結果はTestInformationです')
|
401
|
+
test_info: TestInformation = first_result
|
402
|
+
|
403
|
+
curernt_display_object = [test_info]
|
404
|
+
|
405
|
+
while True :
|
406
|
+
# 各テストケースの結果表示
|
407
|
+
for i, result in enumerate(results):
|
408
|
+
if isinstance(result, LabeledTestCaseResult):
|
409
|
+
console.print(create_renderable_test_result(i, result))
|
410
|
+
update_test_info(test_info, result)
|
411
|
+
else:
|
412
|
+
raise ValueError('テスト結果がyieldする型はLabeledTestCaseResultです')
|
413
|
+
|
414
|
+
console.print(create_renderable_test_info(test_info))
|
254
415
|
|
255
416
|
|
256
417
|
def run_test(path_of_code: str) -> None:
|
@@ -265,12 +426,13 @@ def run_test(path_of_code: str) -> None:
|
|
265
426
|
html = file.read()
|
266
427
|
|
267
428
|
test_cases = create_testcases_from_html(html)
|
268
|
-
print(f'{path_of_code}をテストします。\n' + '-' * 20 + '\n')
|
269
429
|
test_results = judge_code_from(test_cases, path_of_code)
|
270
|
-
|
271
|
-
|
272
|
-
print(output)
|
430
|
+
render_results(test_results)
|
273
431
|
|
274
432
|
|
275
433
|
def test(*args: str) -> None:
|
276
|
-
execute_files(
|
434
|
+
execute_files(
|
435
|
+
*args,
|
436
|
+
func=run_test,
|
437
|
+
target_filetypes=INTERPRETED_LANGUAGES + COMPILED_LANGUAGES,
|
438
|
+
)
|
atcdr/util/execute.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Callable, List
|
3
|
+
|
4
|
+
import questionary as q
|
5
|
+
from rich import print
|
6
|
+
|
7
|
+
from atcdr.util.filetype import FILE_EXTENSIONS, Filename, Lang
|
8
|
+
|
9
|
+
|
10
|
+
def execute_files(
|
11
|
+
*args: str, func: Callable[[Filename], None], target_filetypes: List[Lang]
|
12
|
+
) -> None:
|
13
|
+
target_extensions = [FILE_EXTENSIONS[lang] for lang in target_filetypes]
|
14
|
+
|
15
|
+
files = [
|
16
|
+
file
|
17
|
+
for file in os.listdir('.')
|
18
|
+
if os.path.isfile(file) and os.path.splitext(file)[1] in target_extensions
|
19
|
+
]
|
20
|
+
|
21
|
+
if not files:
|
22
|
+
print(
|
23
|
+
'対象のファイルが見つかりません.\n対象ファイルが存在するディレクトリーに移動してから実行してください。'
|
24
|
+
)
|
25
|
+
return
|
26
|
+
|
27
|
+
if not args:
|
28
|
+
if len(files) == 1:
|
29
|
+
func(files[0])
|
30
|
+
else:
|
31
|
+
target_file = q.select(
|
32
|
+
message='複数のファイルが見つかりました.ファイルを選択してください:',
|
33
|
+
choices=[q.Choice(title=file, value=file) for file in files],
|
34
|
+
instruction='\n 十字キーで移動, [enter]で実行',
|
35
|
+
pointer='❯❯❯',
|
36
|
+
qmark='',
|
37
|
+
style=q.Style(
|
38
|
+
[
|
39
|
+
('qmark', 'fg:#2196F3 bold'),
|
40
|
+
('question', 'fg:#2196F3 bold'),
|
41
|
+
('answer', 'fg:#FFB300 bold'),
|
42
|
+
('pointer', 'fg:#FFB300 bold'),
|
43
|
+
('highlighted', 'fg:#FFB300 bold'),
|
44
|
+
('selected', 'fg:#FFB300 bold'),
|
45
|
+
]
|
46
|
+
),
|
47
|
+
).ask()
|
48
|
+
list(map(func, [target_file]))
|
49
|
+
else:
|
50
|
+
target_files = set()
|
51
|
+
for arg in args:
|
52
|
+
if arg == '*':
|
53
|
+
target_files.update(files)
|
54
|
+
elif arg.startswith('*.'):
|
55
|
+
ext = arg[1:] # ".py" のような拡張子を取得
|
56
|
+
target_files.update(file for file in files if file.endswith(ext))
|
57
|
+
else:
|
58
|
+
if arg in files:
|
59
|
+
target_files.add(arg)
|
60
|
+
else:
|
61
|
+
print(f'エラー: {arg} は存在しません。')
|
62
|
+
|
63
|
+
list(map(func, target_files))
|