AtCoderStudyBooster 0.2__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/test.py +297 -153
- atcdr/util/filetype.py +22 -8
- {atcoderstudybooster-0.2.dist-info → atcoderstudybooster-0.3.dist-info}/METADATA +1 -1
- {atcoderstudybooster-0.2.dist-info → atcoderstudybooster-0.3.dist-info}/RECORD +6 -6
- {atcoderstudybooster-0.2.dist-info → atcoderstudybooster-0.3.dist-info}/WHEEL +0 -0
- {atcoderstudybooster-0.2.dist-info → atcoderstudybooster-0.3.dist-info}/entry_points.txt +0 -0
atcdr/test.py
CHANGED
@@ -2,19 +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
9
|
from bs4 import BeautifulSoup as bs
|
10
|
-
from rich.console import Console
|
10
|
+
from rich.console import Console, Group, RenderableType
|
11
11
|
from rich.markup import escape
|
12
12
|
from rich.panel import Panel
|
13
|
+
from rich.rule import Rule
|
14
|
+
from rich.style import Style
|
15
|
+
from rich.syntax import Syntax
|
13
16
|
from rich.table import Table
|
14
17
|
from rich.text import Text
|
15
18
|
|
16
19
|
from atcdr.util.execute import execute_files
|
17
|
-
from atcdr.util.filetype import
|
20
|
+
from atcdr.util.filetype import (
|
21
|
+
COMPILED_LANGUAGES,
|
22
|
+
INTERPRETED_LANGUAGES,
|
23
|
+
Lang,
|
24
|
+
detect_language,
|
25
|
+
lang2str,
|
26
|
+
)
|
18
27
|
|
19
28
|
|
20
29
|
@dataclass
|
@@ -30,12 +39,13 @@ class LabeledTestCase:
|
|
30
39
|
|
31
40
|
|
32
41
|
class ResultStatus(Enum):
|
33
|
-
|
34
|
-
|
42
|
+
AC = 'Accepted'
|
43
|
+
WA = 'Wrong Answer'
|
35
44
|
TLE = 'Time Limit Exceeded'
|
45
|
+
MLE = 'Memory Limit Exceeded'
|
36
46
|
RE = 'Runtime Error'
|
37
|
-
|
38
|
-
|
47
|
+
CE = 'Compilation Error'
|
48
|
+
WJ = 'Juding ...'
|
39
49
|
|
40
50
|
|
41
51
|
@dataclass
|
@@ -50,10 +60,20 @@ class TestCaseResult:
|
|
50
60
|
class LabeledTestCaseResult:
|
51
61
|
label: str
|
52
62
|
testcase: TestCase
|
53
|
-
# TODO : 実はラベル自体を使わない方がいいかもしれない.ラベルという概念が削除してプリントするときに適当にTest1, Test2と適当に名前をつけてもいいかも.
|
54
63
|
result: TestCaseResult
|
55
64
|
|
56
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
|
+
|
57
77
|
def create_testcases_from_html(html: str) -> List[LabeledTestCase]:
|
58
78
|
soup = bs(html, 'html.parser')
|
59
79
|
test_cases = []
|
@@ -86,192 +106,312 @@ def create_testcases_from_html(html: str) -> List[LabeledTestCase]:
|
|
86
106
|
|
87
107
|
|
88
108
|
def run_code(cmd: list, case: TestCase) -> TestCaseResult:
|
109
|
+
start_time = time.time()
|
89
110
|
try:
|
90
|
-
start_time = time.time()
|
91
111
|
proc = subprocess.run(
|
92
112
|
cmd, input=case.input, text=True, capture_output=True, timeout=4
|
93
113
|
)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
if
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
+
)
|
102
123
|
|
103
|
-
|
104
|
-
|
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
|
+
)
|
105
131
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
executed_time=execution_time,
|
110
|
-
passed=ResultStatus.WA,
|
111
|
-
)
|
132
|
+
# 実際の出力と期待される出力を比較
|
133
|
+
actual_output = proc.stdout.strip()
|
134
|
+
expected_output = case.output.strip()
|
112
135
|
|
136
|
+
if actual_output != expected_output:
|
113
137
|
return TestCaseResult(
|
114
|
-
output=actual_output,
|
138
|
+
output=actual_output,
|
139
|
+
executed_time=executed_time,
|
140
|
+
passed=ResultStatus.WA,
|
115
141
|
)
|
116
|
-
|
142
|
+
else:
|
117
143
|
return TestCaseResult(
|
118
|
-
output=
|
144
|
+
output=actual_output, executed_time=executed_time, passed=ResultStatus.AC
|
119
145
|
)
|
120
|
-
except Exception as e:
|
121
|
-
return TestCaseResult(output=str(e), executed_time=None, passed=ResultStatus.RE)
|
122
|
-
|
123
146
|
|
124
|
-
def run_python(path: str, case: TestCase) -> TestCaseResult:
|
125
|
-
return run_code(['python3', path], case)
|
126
147
|
|
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
|
+
}
|
127
156
|
|
128
|
-
|
129
|
-
|
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
|
+
}
|
130
163
|
|
131
164
|
|
132
|
-
def
|
165
|
+
def run_compile(path: str, lang: Lang) -> Tuple[str, subprocess.CompletedProcess]:
|
133
166
|
with tempfile.NamedTemporaryFile(delete=True) as tmp:
|
134
167
|
exec_path = tmp.name
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
return run_code([exec_path], case)
|
156
206
|
|
207
|
+
cmd = [
|
208
|
+
arg.format(exec_path=exe_path) for arg in LANGUAGE_RUN_COMMANDS[lang]
|
209
|
+
]
|
157
210
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
['rustc', path, '-o', exec_path], capture_output=True, text=True
|
163
|
-
)
|
164
|
-
if compile_result.returncode != 0:
|
165
|
-
return TestCaseResult(
|
166
|
-
output=compile_result.stderr, executed_time=None, passed=ResultStatus.CE
|
167
|
-
)
|
168
|
-
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
|
+
)
|
169
215
|
|
216
|
+
if os.path.exists(exe_path):
|
217
|
+
os.remove(exe_path)
|
170
218
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
219
|
+
elif lang in INTERPRETED_LANGUAGES:
|
220
|
+
yield TestInformation(
|
221
|
+
lang=lang,
|
222
|
+
sourcename=path,
|
223
|
+
case_number=len(lcases),
|
176
224
|
)
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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',
|
242
|
+
}
|
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
|
+
),
|
193
270
|
}
|
194
271
|
|
195
272
|
|
196
|
-
def
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
200
278
|
)
|
201
|
-
|
202
|
-
if lang is not None:
|
203
|
-
return LANGUAGE_RUNNERS.get(lang)
|
204
|
-
return None
|
279
|
+
total_count = test_info.case_number
|
205
280
|
|
281
|
+
# 結果に応じたスタイル付きのテキストを取得
|
282
|
+
status_text = STATUS_TEXT_MAP[test_info.result_summary]
|
206
283
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
for lcase in lcases
|
217
|
-
]
|
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
|
+
),
|
292
|
+
)
|
218
293
|
|
294
|
+
components.append(Panel(header_text, expand=False))
|
219
295
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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)
|
225
306
|
|
307
|
+
return Group(*components)
|
226
308
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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,
|
322
|
+
]
|
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],
|
231
347
|
)
|
232
|
-
total_count = len(results)
|
233
348
|
|
234
|
-
#
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
f'{success_count}/{total_count} ',
|
239
|
-
'green' if success_count == total_count else 'red',
|
240
|
-
),
|
349
|
+
# 以下の部分は if-else ブロックの外に移動
|
350
|
+
status_header = Text.assemble(
|
351
|
+
'ステータス ',
|
352
|
+
STATUS_TEXT_MAP[test_result.result.passed], # status_text をここに追加
|
241
353
|
)
|
242
|
-
console.print(Panel(header_text, expand=False))
|
243
354
|
|
244
|
-
|
245
|
-
|
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
|
+
)
|
360
|
+
|
361
|
+
table = Table(show_header=True, header_style='bold')
|
362
|
+
table.add_column('入力', style='cyan', min_width=10)
|
363
|
+
|
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),
|
373
|
+
)
|
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)
|
380
|
+
)
|
381
|
+
|
382
|
+
components = [
|
383
|
+
rule,
|
384
|
+
status_header,
|
385
|
+
execution_time_text if execution_time_text else '',
|
386
|
+
table,
|
387
|
+
]
|
388
|
+
|
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 :
|
246
406
|
# 各テストケースの結果表示
|
247
407
|
for i, result in enumerate(results):
|
248
|
-
if result
|
249
|
-
|
250
|
-
|
251
|
-
console.print(f'[bold]ステータス:[/] {status_text}')
|
252
|
-
|
253
|
-
else:
|
254
|
-
status_text = f'[red]{CROSS_MARK} {result.result.passed.value}[/]'
|
255
|
-
console.rule(title=f'No.{i+1} {result.label}', style='red')
|
256
|
-
console.print(f'[bold]ステータス:[/] {status_text}')
|
257
|
-
|
258
|
-
if result.result.executed_time is not None:
|
259
|
-
console.print(f'[bold]実行時間:[/] {result.result.executed_time} ms')
|
260
|
-
|
261
|
-
table = Table(show_header=True, header_style='bold')
|
262
|
-
table.add_column('入力', style='cyan', min_width=10)
|
263
|
-
if result.result.passed != ResultStatus.AC:
|
264
|
-
table.add_column('出力', style='red', min_width=10)
|
265
|
-
table.add_column('正解の出力', style='green', min_width=10)
|
266
|
-
table.add_row(
|
267
|
-
escape(result.testcase.input),
|
268
|
-
escape(result.result.output),
|
269
|
-
escape(result.testcase.output),
|
270
|
-
)
|
408
|
+
if isinstance(result, LabeledTestCaseResult):
|
409
|
+
console.print(create_renderable_test_result(i, result))
|
410
|
+
update_test_info(test_info, result)
|
271
411
|
else:
|
272
|
-
|
273
|
-
|
274
|
-
|
412
|
+
raise ValueError('テスト結果がyieldする型はLabeledTestCaseResultです')
|
413
|
+
|
414
|
+
console.print(create_renderable_test_info(test_info))
|
275
415
|
|
276
416
|
|
277
417
|
def run_test(path_of_code: str) -> None:
|
@@ -287,8 +427,12 @@ def run_test(path_of_code: str) -> None:
|
|
287
427
|
|
288
428
|
test_cases = create_testcases_from_html(html)
|
289
429
|
test_results = judge_code_from(test_cases, path_of_code)
|
290
|
-
render_results(
|
430
|
+
render_results(test_results)
|
291
431
|
|
292
432
|
|
293
433
|
def test(*args: str) -> None:
|
294
|
-
execute_files(
|
434
|
+
execute_files(
|
435
|
+
*args,
|
436
|
+
func=run_test,
|
437
|
+
target_filetypes=INTERPRETED_LANGUAGES + COMPILED_LANGUAGES,
|
438
|
+
)
|
atcdr/util/filetype.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
import os
|
1
2
|
from enum import Enum
|
2
|
-
from typing import Dict, List, TypeAlias
|
3
|
+
from typing import Dict, List, Optional, TypeAlias
|
3
4
|
|
4
5
|
# ファイル名と拡張子の型エイリアスを定義
|
5
6
|
Filename: TypeAlias = str
|
@@ -23,7 +24,7 @@ class Lang(Enum):
|
|
23
24
|
|
24
25
|
|
25
26
|
# ファイル拡張子と対応する言語の辞書
|
26
|
-
FILE_EXTENSIONS: Dict[Lang,
|
27
|
+
FILE_EXTENSIONS: Dict[Lang, str] = {
|
27
28
|
Lang.PYTHON: '.py',
|
28
29
|
Lang.JAVASCRIPT: '.js',
|
29
30
|
Lang.JAVA: '.java',
|
@@ -39,26 +40,31 @@ FILE_EXTENSIONS: Dict[Lang, Extension] = {
|
|
39
40
|
Lang.JSON: '.json',
|
40
41
|
}
|
41
42
|
|
43
|
+
# ドキュメント言語のリスト
|
42
44
|
DOCUMENT_LANGUAGES: List[Lang] = [
|
43
45
|
Lang.HTML,
|
44
46
|
Lang.MARKDOWN,
|
45
47
|
Lang.JSON,
|
46
48
|
]
|
47
49
|
|
48
|
-
#
|
49
|
-
|
50
|
-
Lang.PYTHON,
|
51
|
-
Lang.JAVASCRIPT,
|
50
|
+
# コンパイル型言語のリスト
|
51
|
+
COMPILED_LANGUAGES: List[Lang] = [
|
52
52
|
Lang.JAVA,
|
53
53
|
Lang.C,
|
54
54
|
Lang.CPP,
|
55
55
|
Lang.CSHARP,
|
56
|
-
Lang.RUBY,
|
57
|
-
Lang.PHP,
|
58
56
|
Lang.GO,
|
59
57
|
Lang.RUST,
|
60
58
|
]
|
61
59
|
|
60
|
+
# インタプリター型言語のリスト
|
61
|
+
INTERPRETED_LANGUAGES: List[Lang] = [
|
62
|
+
Lang.PYTHON,
|
63
|
+
Lang.JAVASCRIPT,
|
64
|
+
Lang.RUBY,
|
65
|
+
Lang.PHP,
|
66
|
+
]
|
67
|
+
|
62
68
|
|
63
69
|
def str2lang(lang: str) -> Lang:
|
64
70
|
lang_map = {
|
@@ -89,3 +95,11 @@ def str2lang(lang: str) -> Lang:
|
|
89
95
|
|
90
96
|
def lang2str(lang: Lang) -> str:
|
91
97
|
return lang.value
|
98
|
+
|
99
|
+
|
100
|
+
def detect_language(path: str) -> Optional[Lang]:
|
101
|
+
ext = os.path.splitext(path)[1] # ファイルの拡張子を取得
|
102
|
+
lang = next(
|
103
|
+
(lang for lang, extension in FILE_EXTENSIONS.items() if extension == ext), None
|
104
|
+
)
|
105
|
+
return lang
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: AtCoderStudyBooster
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3
|
4
4
|
Summary: A tool to download and manage AtCoder problems.
|
5
5
|
Project-URL: Homepage, https://github.com/yuta6/AtCoderStudyBooster
|
6
6
|
Author-email: yuta6 <46110512+yuta6@users.noreply.github.com>
|
@@ -4,14 +4,14 @@ atcdr/generate.py,sha256=0yWX-5PS-FR6LTaP3muHq6a7rFB2a1Oek48mF45exoA,6972
|
|
4
4
|
atcdr/main.py,sha256=y2IkXwcAyKZ_1y5PgU93GpXzo5lKak9oxo0XV_9d5Fo,727
|
5
5
|
atcdr/markdown.py,sha256=jEktnYgrDYcgIuhxRpJImAzNpFmfSPkRikAesfMxAVk,1125
|
6
6
|
atcdr/open.py,sha256=2UlmNWdieoMrPu1xSUWf-8sBB9Y19r0t6V9zDRBSPes,924
|
7
|
-
atcdr/test.py,sha256=
|
7
|
+
atcdr/test.py,sha256=eNHEv_y2tNBW9BbfzbGxRjUjbZB7Skxp1UVZkUJyj24,12021
|
8
8
|
atcdr/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
atcdr/util/cost.py,sha256=0c9H8zLley7xZDLuYU4zJmB8m71qcO1WEIQOoEavD_4,3168
|
10
10
|
atcdr/util/execute.py,sha256=tcYflnVo_38LdaOGDUAuqfSfcA54bTrCaTRShH7kwUw,1750
|
11
|
-
atcdr/util/filetype.py,sha256=
|
11
|
+
atcdr/util/filetype.py,sha256=n8alyOdrItoFZzLIssDoXg7i5LamHM9DaY1legUzOD0,2007
|
12
12
|
atcdr/util/gpt.py,sha256=Lto6SJHZGer8cC_Nq8lJVnaET2R7apFQteo6ZEFpjdM,3304
|
13
13
|
atcdr/util/problem.py,sha256=WprmpOZm6xpyvksIS3ou1uHqFnBO1FUZWadsLziG1bY,2484
|
14
|
-
atcoderstudybooster-0.
|
15
|
-
atcoderstudybooster-0.
|
16
|
-
atcoderstudybooster-0.
|
17
|
-
atcoderstudybooster-0.
|
14
|
+
atcoderstudybooster-0.3.dist-info/METADATA,sha256=xnKGPQ0_6d592dSluNqZ17xodV23Rvi1h7CGFjsIv84,4467
|
15
|
+
atcoderstudybooster-0.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
16
|
+
atcoderstudybooster-0.3.dist-info/entry_points.txt,sha256=_bhz0R7vp2VubKl_eIokDO8Wz9TdqvYA7Q59uWfy6Sk,42
|
17
|
+
atcoderstudybooster-0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|