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/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 Callable, Dict, List, Optional, Union
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 colorama import Fore
12
-
13
- from atcdr.util.filename import FILE_EXTENSIONS, SOURCE_LANGUAGES, Lang, execute_files
14
-
15
- colorama.init(autoreset=True)
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
- CE = 'Compilation Error'
32
- MLE = 'Memory Limit Exceeded'
42
+ AC = 'Accepted'
43
+ WA = 'Wrong Answer'
33
44
  TLE = 'Time Limit Exceeded'
45
+ MLE = 'Memory Limit Exceeded'
34
46
  RE = 'Runtime Error'
35
- WA = 'Wrong Answer'
36
- AC = 'Accepted'
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
- end_time = time.time()
93
-
94
- execution_time = int((end_time - start_time) * 1000)
95
-
96
- if proc.returncode != 0:
97
- return TestCaseResult(
98
- output=proc.stderr, executed_time=None, passed=ResultStatus.RE
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
- actual_output = proc.stdout.strip()
102
- expected_output = case.output.strip()
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
- if actual_output != expected_output:
105
- return TestCaseResult(
106
- output=actual_output,
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, executed_time=execution_time, passed=ResultStatus.AC
138
+ output=actual_output,
139
+ executed_time=executed_time,
140
+ passed=ResultStatus.WA,
113
141
  )
114
- except subprocess.TimeoutExpired:
142
+ else:
115
143
  return TestCaseResult(
116
- output='Time Limit Exceeded', executed_time=None, passed=ResultStatus.TLE
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
- def run_python(path: str, case: TestCase) -> TestCaseResult:
123
- return run_code(['python3', path], case)
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
- def run_javascript(path: str, case: TestCase) -> TestCaseResult:
127
- return run_code(['node', path], case)
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 run_c(path: str, case: TestCase) -> TestCaseResult:
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
- compile_result = subprocess.run(
134
- ['gcc', path, '-o', exec_path], capture_output=True, text=True
135
- )
136
- if compile_result.returncode != 0:
137
- return TestCaseResult(
138
- output=compile_result.stderr, executed_time=None, passed=ResultStatus.CE
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
- def run_cpp(path: str, case: TestCase) -> TestCaseResult:
146
- with tempfile.NamedTemporaryFile(delete=True) as tmp:
147
- exec_path = tmp.name
148
- compile_result = subprocess.run(
149
- ['g++', path, '-o', exec_path], capture_output=True, text=True
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
- return TestCaseResult(
153
- output=compile_result.stderr, executed_time=None, passed=ResultStatus.CE
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
- def run_rust(path: str, case: TestCase) -> TestCaseResult:
161
- with tempfile.NamedTemporaryFile(delete=True) as tmp:
162
- exec_path = tmp.name
163
- compile_result = subprocess.run(
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
- def run_java(path: str, case: TestCase) -> TestCaseResult:
176
- compile_result = subprocess.run(['javac', path], capture_output=True, text=True)
177
- if compile_result.returncode != 0:
178
- return TestCaseResult(
179
- output=compile_result.stderr, executed_time=None, passed=ResultStatus.CE
219
+ elif lang in INTERPRETED_LANGUAGES:
220
+ yield TestInformation(
221
+ lang=lang,
222
+ sourcename=path,
223
+ case_number=len(lcases),
180
224
  )
181
- class_file = os.path.splitext(path)[0]
182
- try:
183
- return run_code(['java', class_file], case)
184
- finally:
185
- class_path = class_file + '.class'
186
- if os.path.exists(class_path):
187
- os.remove(class_path)
188
-
189
-
190
- LANGUAGE_RUNNERS: Dict[Lang, Callable[[str, TestCase], TestCaseResult]] = {
191
- Lang.PYTHON: run_python,
192
- Lang.JAVASCRIPT: run_javascript,
193
- Lang.C: run_c,
194
- Lang.CPP: run_cpp,
195
- Lang.RUST: run_rust,
196
- Lang.JAVA: run_java,
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
- def choose_lang(path: str) -> Optional[Callable[[str, TestCase], TestCaseResult]]:
201
- ext = os.path.splitext(path)[1]
202
- lang = next(
203
- (lang for lang, extension in FILE_EXTENSIONS.items() if extension == ext), None
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
- def judge_code_from(
212
- lcases: List[LabeledTestCase], path: str
213
- ) -> List[LabeledTestCaseResult]:
214
- runner = choose_lang(path)
215
- if runner is None:
216
- raise ValueError(f'ランナーが見つかりませんでした。指定されたパス: {path}')
217
-
218
- return [
219
- LabeledTestCaseResult(lcase.label, lcase.case, runner(path, lcase.case))
220
- for lcase in lcases
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
- CHECK_MARK = '\u2713'
225
- CROSS_MARK = '\u00d7'
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
- def render_result(lresult: LabeledTestCaseResult) -> str:
229
- output = f'{Fore.CYAN}{lresult.label} of Test:\n'
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 == ResultStatus.AC:
234
- output += (
235
- Fore.GREEN + f'{CHECK_MARK} Accepted !! Time: {result.executed_time} ms\n'
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
- elif result.passed == ResultStatus.WA:
238
- output += (
239
- Fore.RED
240
- + f'{CROSS_MARK} Wrong Answer ! Time: {result.executed_time} ms\nOutput:\n{result.output}\nExpected Output:\n{testcase.output}\n'
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
- output += Fore.RESET
382
+ components = [
383
+ rule,
384
+ status_header,
385
+ execution_time_text if execution_time_text else '',
386
+ table,
387
+ ]
252
388
 
253
- return output
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
- output = '\n'.join(render_result(lresult) for lresult in test_results)
271
-
272
- print(output)
430
+ render_results(test_results)
273
431
 
274
432
 
275
433
  def test(*args: str) -> None:
276
- execute_files(*args, func=run_test, target_filetypes=SOURCE_LANGUAGES)
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))