AtCoderStudyBooster 0.3.3__py3-none-any.whl → 0.4.1__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 +139 -234
- atcdr/generate.py +19 -24
- atcdr/login.py +10 -11
- atcdr/logout.py +4 -3
- atcdr/markdown.py +9 -5
- atcdr/open.py +5 -4
- atcdr/submit.py +21 -24
- atcdr/test.py +21 -16
- atcdr/util/fileops.py +5 -4
- atcdr/util/gpt.py +10 -12
- atcdr/util/i18n.py +317 -0
- atcdr/util/parse.py +16 -11
- atcdr/util/problem.py +29 -75
- atcdr/util/session.py +15 -14
- atcoderstudybooster-0.4.1.dist-info/METADATA +248 -0
- atcoderstudybooster-0.4.1.dist-info/RECORD +22 -0
- atcoderstudybooster-0.3.3.dist-info/METADATA +0 -213
- atcoderstudybooster-0.3.3.dist-info/RECORD +0 -21
- {atcoderstudybooster-0.3.3.dist-info → atcoderstudybooster-0.4.1.dist-info}/WHEEL +0 -0
- {atcoderstudybooster-0.3.3.dist-info → atcoderstudybooster-0.4.1.dist-info}/entry_points.txt +0 -0
atcdr/download.py
CHANGED
@@ -1,165 +1,111 @@
|
|
1
|
-
import os
|
2
1
|
import re
|
3
2
|
import time
|
4
|
-
from
|
3
|
+
from itertools import groupby, product
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import List, Tuple, Union, cast
|
5
6
|
|
6
7
|
import questionary as q
|
8
|
+
import requests
|
7
9
|
import rich_click as click
|
8
10
|
from rich import print
|
9
11
|
from rich.prompt import Prompt
|
10
12
|
|
11
13
|
from atcdr.util.filetype import FILE_EXTENSIONS, Lang
|
14
|
+
from atcdr.util.i18n import _, i18n
|
12
15
|
from atcdr.util.parse import ProblemHTML
|
13
|
-
from atcdr.util.problem import Contest,
|
16
|
+
from atcdr.util.problem import Contest, Problem
|
14
17
|
from atcdr.util.session import load_session
|
15
18
|
|
16
19
|
|
17
20
|
class Downloader:
|
18
|
-
def __init__(self) -> None:
|
19
|
-
self.session =
|
21
|
+
def __init__(self, session: requests.Session) -> None:
|
22
|
+
self.session = session
|
20
23
|
|
21
24
|
def get(self, problem: Problem) -> ProblemHTML:
|
22
25
|
session = self.session
|
23
26
|
retry_attempts = 3
|
24
27
|
retry_wait = 1 # 1 second
|
25
28
|
|
26
|
-
for
|
29
|
+
for attempt in range(retry_attempts):
|
27
30
|
response = session.get(problem.url)
|
28
31
|
if response.status_code == 200:
|
29
32
|
return ProblemHTML(response.text)
|
30
33
|
elif response.status_code == 429:
|
31
34
|
print(
|
32
|
-
f'[bold yellow][Error {response.status_code}][/bold yellow]
|
35
|
+
f'[bold yellow][Error {response.status_code}][/bold yellow] '
|
36
|
+
+ _('retry_problem', problem)
|
33
37
|
)
|
34
38
|
time.sleep(retry_wait)
|
35
39
|
elif 300 <= response.status_code < 400:
|
36
40
|
print(
|
37
|
-
f'[bold yellow][Error {response.status_code}][/bold yellow]
|
41
|
+
f'[bold yellow][Error {response.status_code}][/bold yellow] '
|
42
|
+
+ _('redirect_occurred', problem)
|
38
43
|
)
|
39
44
|
elif 400 <= response.status_code < 500:
|
40
45
|
print(
|
41
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
46
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
47
|
+
+ _('problem_not_found', problem)
|
42
48
|
)
|
43
49
|
break
|
44
50
|
elif 500 <= response.status_code < 600:
|
45
51
|
print(
|
46
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
52
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
53
|
+
+ _('server_error', problem)
|
47
54
|
)
|
48
55
|
break
|
49
56
|
else:
|
50
57
|
print(
|
51
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
58
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
59
|
+
+ _('html_fetch_failed', problem)
|
52
60
|
)
|
53
61
|
break
|
54
62
|
return ProblemHTML('')
|
55
63
|
|
56
64
|
|
57
|
-
def mkdir(path: str) -> None:
|
58
|
-
if not os.path.exists(path):
|
59
|
-
os.makedirs(path)
|
60
|
-
print(f'[bold green][+][/bold green] フォルダー: {path} を作成しました')
|
61
|
-
|
62
|
-
|
63
|
-
class GenerateMode:
|
64
|
-
@staticmethod
|
65
|
-
def gene_path_on_diff(base: str, problem: Problem) -> str:
|
66
|
-
return (
|
67
|
-
os.path.join(base, problem.label, f'{problem.contest.number:03}')
|
68
|
-
if problem.contest.number
|
69
|
-
else os.path.join(base, problem.label, problem.contest.contest)
|
70
|
-
)
|
71
|
-
|
72
|
-
@staticmethod
|
73
|
-
def gene_path_on_num(base: str, problem: Problem) -> str:
|
74
|
-
return (
|
75
|
-
os.path.join(base, f'{problem.contest.number:03}', problem.label)
|
76
|
-
if problem.contest.number
|
77
|
-
else os.path.join(base, problem.contest.contest, problem.label)
|
78
|
-
)
|
79
|
-
|
80
|
-
|
81
65
|
def title_to_filename(title: str) -> str:
|
82
66
|
title = re.sub(r'[\\/*?:"<>| !@#$%^&()+=\[\]{};,\']', '', title)
|
83
67
|
title = re.sub(r'.*?-', '', title)
|
84
68
|
return title
|
85
69
|
|
86
70
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
for problem in problems:
|
92
|
-
problem_content = downloader.get(problem)
|
93
|
-
if not problem_content:
|
94
|
-
print(f'[bold red][Error][/] {problem}の保存に失敗しました')
|
95
|
-
continue
|
96
|
-
|
97
|
-
dir_path = gene_path(base_path, problem)
|
98
|
-
mkdir(dir_path)
|
99
|
-
|
100
|
-
problem_content.repair_me()
|
101
|
-
|
102
|
-
title = problem_content.title or problem.label
|
103
|
-
title = title_to_filename(title)
|
104
|
-
|
105
|
-
html_path = os.path.join(dir_path, title + FILE_EXTENSIONS[Lang.HTML])
|
106
|
-
with open(html_path, 'w', encoding='utf-8') as file:
|
107
|
-
file.write(problem_content.html)
|
108
|
-
print(f'[bold green][+][/bold green] ファイルを保存しました :{html_path}')
|
109
|
-
|
110
|
-
md = problem_content.make_problem_markdown('ja')
|
111
|
-
md_path = os.path.join(dir_path, title + FILE_EXTENSIONS[Lang.MARKDOWN])
|
112
|
-
with open(md_path, 'w', encoding='utf-8') as file:
|
113
|
-
file.write(md)
|
114
|
-
print(f'[bold green][+][/bold green] ファイルを保存しました :{md_path}')
|
115
|
-
|
71
|
+
def save_problem(problem: Problem, path: Path, session: requests.Session) -> None:
|
72
|
+
"""1つの問題を保存"""
|
73
|
+
downloader = Downloader(session)
|
74
|
+
problem_content = downloader.get(problem)
|
116
75
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
return list(range(start, end + 1))
|
121
|
-
|
122
|
-
|
123
|
-
def parse_diff_range(match: re.Match) -> List[Diff]:
|
124
|
-
start, end = match.groups()
|
125
|
-
start_index = min(ord(start.upper()), ord(end.upper()))
|
126
|
-
end_index = max(ord(start.upper()), ord(end.upper()))
|
127
|
-
return [Diff(chr(i)) for i in range(start_index, end_index + 1)]
|
128
|
-
|
129
|
-
|
130
|
-
def convert_arg(arg: str) -> Union[List[int], List[Diff]]:
|
131
|
-
if arg.isdigit():
|
132
|
-
return [int(arg)]
|
133
|
-
elif arg.isalpha() and len(arg) == 1:
|
134
|
-
return [Diff(arg)]
|
135
|
-
elif match := re.match(r'^(\d+)\.\.(\d+)$', arg):
|
136
|
-
return parse_range(match)
|
137
|
-
elif match := re.match(r'^([A-Z])\.\.([A-Z])$', arg, re.IGNORECASE):
|
138
|
-
return parse_diff_range(match)
|
139
|
-
else:
|
140
|
-
raise ValueError(f'{arg}は認識できません')
|
76
|
+
if not problem_content:
|
77
|
+
print('[bold red][Error][/] ' + _('save_failed', problem))
|
78
|
+
return
|
141
79
|
|
80
|
+
# ディレクトリ作成(pathをそのまま使用)
|
81
|
+
path.mkdir(parents=True, exist_ok=True)
|
142
82
|
|
143
|
-
|
144
|
-
|
83
|
+
problem_content.repair_me()
|
84
|
+
title = title_to_filename(problem_content.title or problem.label)
|
145
85
|
|
86
|
+
# HTMLファイル保存
|
87
|
+
html_path = path / (title + FILE_EXTENSIONS[Lang.HTML])
|
88
|
+
html_path.write_text(problem_content.html, encoding='utf-8')
|
89
|
+
print('[bold green][+][/bold green] ' + _('file_saved', html_path))
|
146
90
|
|
147
|
-
|
148
|
-
|
91
|
+
# Markdownファイル保存
|
92
|
+
md = problem_content.make_problem_markdown(i18n.language)
|
93
|
+
md_path = path / (title + FILE_EXTENSIONS[Lang.MARKDOWN])
|
94
|
+
md_path.write_text(md, encoding='utf-8')
|
95
|
+
print('[bold green][+][/bold green] ' + _('file_saved', md_path))
|
149
96
|
|
150
97
|
|
151
|
-
def interactive_download() -> None:
|
152
|
-
CONTEST = '1.
|
153
|
-
|
154
|
-
|
155
|
-
END = '4. 終了する'
|
98
|
+
def interactive_download(session) -> None:
|
99
|
+
CONTEST = '1. ' + _('solve_contest_problems')
|
100
|
+
ONE_FILE = '2. ' + _('download_one_problem')
|
101
|
+
END = '3. ' + _('exit')
|
156
102
|
|
157
103
|
choice = q.select(
|
158
|
-
message='
|
104
|
+
message=_('download_atcoder_html'),
|
159
105
|
qmark='',
|
160
106
|
pointer='❯❯❯',
|
161
|
-
choices=[CONTEST,
|
162
|
-
instruction='\n
|
107
|
+
choices=[CONTEST, ONE_FILE, END],
|
108
|
+
instruction='\n ' + _('navigate_with_arrows'),
|
163
109
|
style=q.Style(
|
164
110
|
[
|
165
111
|
('question', 'fg:#2196F3 bold'),
|
@@ -172,151 +118,110 @@ def interactive_download() -> None:
|
|
172
118
|
).ask()
|
173
119
|
|
174
120
|
if choice == CONTEST:
|
175
|
-
name = Prompt.ask(
|
176
|
-
'コンテスト名を入力してください (例: abc012, abs, typical90)',
|
177
|
-
)
|
178
|
-
|
179
|
-
problems = Contest(name=name).problems(session=load_session())
|
180
|
-
if not problems:
|
181
|
-
print(f'[red][Error][/red] コンテスト名が間違っています: {name}')
|
182
|
-
return
|
183
|
-
|
184
|
-
generate_problem_directory('.', problems, GenerateMode.gene_path_on_num)
|
185
|
-
|
186
|
-
elif choice == PRACTICE:
|
187
|
-
difficulty = Prompt.ask(
|
188
|
-
'難易度を入力してください (例: A)',
|
189
|
-
)
|
121
|
+
name = Prompt.ask(_('input_contest_name'))
|
190
122
|
try:
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
'
|
196
|
-
)
|
197
|
-
if number_str.isdigit():
|
198
|
-
contest_numbers = [int(number_str)]
|
199
|
-
elif match := re.match(r'^\d+\.\.\d+$', number_str):
|
200
|
-
contest_numbers = parse_range(match)
|
201
|
-
else:
|
202
|
-
raise ValueError('数字の範囲の形式が間違っています')
|
203
|
-
|
204
|
-
problems = [
|
205
|
-
Problem(contest=Contest('abc', number), difficulty=difficulty)
|
206
|
-
for number in contest_numbers
|
207
|
-
]
|
208
|
-
|
209
|
-
generate_problem_directory('.', problems, GenerateMode.gene_path_on_diff)
|
123
|
+
contest = Contest(name, session)
|
124
|
+
for problem in contest.problems:
|
125
|
+
save_problem(problem, Path(contest.name) / problem.label, session)
|
126
|
+
except ValueError as e:
|
127
|
+
print(f'[red][Error][/red] {e}')
|
210
128
|
|
211
129
|
elif choice == ONE_FILE:
|
212
|
-
name = Prompt.ask(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
generate_problem_directory('.', [problem], GenerateMode.gene_path_on_num)
|
130
|
+
name = Prompt.ask(_('input_contest_name'))
|
131
|
+
try:
|
132
|
+
contest = Contest(name, session)
|
133
|
+
problem = q.select(
|
134
|
+
message=_('which_problem_download'),
|
135
|
+
qmark='',
|
136
|
+
pointer='❯❯❯',
|
137
|
+
choices=[
|
138
|
+
q.Choice(title=f'{p.label:10} | {p.url}', value=p)
|
139
|
+
for p in contest.problems
|
140
|
+
],
|
141
|
+
instruction='\n ' + _('navigate_with_arrows'),
|
142
|
+
style=q.Style(
|
143
|
+
[
|
144
|
+
('question', 'fg:#2196F3 bold'),
|
145
|
+
('answer', 'fg:#FFB300 bold'),
|
146
|
+
('pointer', 'fg:#FFB300 bold'),
|
147
|
+
('highlighted', 'fg:#FFB300 bold'),
|
148
|
+
('selected', 'fg:#FFB300 bold'),
|
149
|
+
]
|
150
|
+
),
|
151
|
+
).ask()
|
152
|
+
save_problem(problem, Path(contest.name) / problem.label, session)
|
153
|
+
except ValueError as e:
|
154
|
+
print(f'[red][Error][/red] {e}')
|
239
155
|
|
240
156
|
elif choice == END:
|
241
|
-
print('[bold red]
|
157
|
+
print('[bold red]' + _('exiting') + '[/]')
|
242
158
|
else:
|
243
|
-
print('[bold red]
|
159
|
+
print('[bold red]' + _('invalid_selection') + '[/]')
|
244
160
|
|
245
161
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
def
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
162
|
+
def plan_download(
|
163
|
+
args: List[str], session: requests.Session
|
164
|
+
) -> List[Tuple[Problem, Path]]:
|
165
|
+
def classify(arg):
|
166
|
+
try:
|
167
|
+
return Contest(arg, session)
|
168
|
+
except ValueError:
|
169
|
+
label = arg
|
170
|
+
return label
|
171
|
+
|
172
|
+
parsed: List[Union['Contest', str]] = list(map(classify, args))
|
173
|
+
|
174
|
+
groups: List[List[Union['Contest', str]]] = [
|
175
|
+
list(group)
|
176
|
+
for _, group in groupby(parsed, key=lambda x: isinstance(x, Contest))
|
177
|
+
]
|
178
|
+
|
179
|
+
if len(groups) == 1:
|
180
|
+
if all(isinstance(x, Contest) for x in groups[0]):
|
181
|
+
contests = cast(List[Contest], groups[0])
|
182
|
+
return [
|
183
|
+
(problem, Path(contest.name) / problem.label)
|
184
|
+
for contest in contests
|
185
|
+
for problem in contest.problems
|
186
|
+
]
|
187
|
+
else:
|
188
|
+
raise ValueError(_('specify_contest_name'))
|
189
|
+
elif len(groups) == 2:
|
190
|
+
result = []
|
191
|
+
for i, j in product(groups[0], groups[1]):
|
192
|
+
if isinstance(i, Contest) and isinstance(j, str):
|
193
|
+
# Contest × Label
|
194
|
+
for problem in i.problems:
|
195
|
+
if problem.label == j:
|
196
|
+
result.append((problem, Path(i.name) / j))
|
197
|
+
elif isinstance(i, str) and isinstance(j, Contest):
|
198
|
+
# Label × Contest
|
199
|
+
for problem in j.problems:
|
200
|
+
if problem.label == i:
|
201
|
+
result.append((problem, Path(i) / j.name))
|
202
|
+
return result
|
203
|
+
else:
|
204
|
+
raise ValueError(_('invalid_download_args'))
|
265
205
|
|
266
|
-
download 120..130 B
|
267
|
-
ABCの120~130番のB問題をダウンロード
|
268
206
|
|
269
|
-
|
270
|
-
|
207
|
+
@click.command(short_help=_('cmd_download'), help=_('cmd_download'))
|
208
|
+
@click.argument('args', nargs=-1)
|
209
|
+
def download(args: List[str]) -> None:
|
271
210
|
"""
|
272
|
-
|
273
|
-
|
274
|
-
|
211
|
+
download abc{001..012} {A..C}
|
212
|
+
download {A..E} abc{001..012}
|
213
|
+
"""
|
214
|
+
session = load_session()
|
275
215
|
|
276
|
-
if
|
277
|
-
|
278
|
-
|
279
|
-
except ValueError:
|
280
|
-
first = str(first)
|
281
|
-
problems = Contest(name=first).problems(session=load_session())
|
282
|
-
if not problems:
|
283
|
-
print(f'[red][Error][red/] コンテスト名が間違っています: {first}')
|
284
|
-
return
|
285
|
-
generate_problem_directory('.', problems, GenerateMode.gene_path_on_num)
|
286
|
-
return
|
216
|
+
if not args:
|
217
|
+
interactive_download(session)
|
218
|
+
return
|
287
219
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
"""
|
294
|
-
)
|
295
|
-
else:
|
296
|
-
second_args = convert_arg(str(second))
|
220
|
+
try:
|
221
|
+
plan = plan_download(args, session)
|
222
|
+
except ValueError as e:
|
223
|
+
print(f'[red][Error][/red] {e}')
|
224
|
+
return
|
297
225
|
|
298
|
-
|
299
|
-
|
300
|
-
second_args_diff = cast(List[Diff], second_args)
|
301
|
-
problems = [
|
302
|
-
Problem(Contest('abc', number), difficulty=diff)
|
303
|
-
for number in first_args_int
|
304
|
-
for diff in second_args_diff
|
305
|
-
]
|
306
|
-
generate_problem_directory(base_path, problems, GenerateMode.gene_path_on_num)
|
307
|
-
elif are_all_diffs(first_args) and are_all_integers(second_args):
|
308
|
-
first_args_diff = cast(List[Diff], first_args)
|
309
|
-
second_args_int = cast(List[int], second_args)
|
310
|
-
problems = [
|
311
|
-
Problem(Contest('abc', number), difficulty=diff)
|
312
|
-
for diff in first_args_diff
|
313
|
-
for number in second_args_int
|
314
|
-
]
|
315
|
-
generate_problem_directory(base_path, problems, GenerateMode.gene_path_on_diff)
|
316
|
-
else:
|
317
|
-
raise ValueError(
|
318
|
-
"""次のような形式で問題を指定してください
|
319
|
-
例 atcdr -d A 120..130 : A問題の120から130をダウンロードします
|
320
|
-
例 atcdr -d 120 : ABCのコンテストの問題をダウンロードします
|
321
|
-
"""
|
322
|
-
)
|
226
|
+
for prob, path in plan:
|
227
|
+
save_problem(prob, path, session)
|
atcdr/generate.py
CHANGED
@@ -17,6 +17,7 @@ from atcdr.util.filetype import (
|
|
17
17
|
str2lang,
|
18
18
|
)
|
19
19
|
from atcdr.util.gpt import ChatGPT, Model, set_api_key
|
20
|
+
from atcdr.util.i18n import _
|
20
21
|
from atcdr.util.parse import ProblemHTML
|
21
22
|
|
22
23
|
|
@@ -58,21 +59,19 @@ def generate_code(file: Filename, lang: Lang, model: Model) -> None:
|
|
58
59
|
system_prompt=f"""You are an excellent programmer. You solve problems in competitive programming.When a user provides you with a problem from a programming contest called AtCoder, including the Problem,Constraints, Input, Output, Input Example, and Output Example, please carefully consider these and solve the problem.Make sure that your output code block contains no more than two blocks. Pay close attention to the Input, Input Example, Output, and Output Example.Create the solution in {lang2str(lang)}.""",
|
59
60
|
model=model,
|
60
61
|
)
|
61
|
-
with console.status(
|
62
|
+
with console.status(_('generating_code', gpt.model.value)):
|
62
63
|
reply = gpt.tell(md)
|
63
64
|
|
64
65
|
code = get_code_from_gpt_output(reply)
|
65
|
-
console.print('[green][+][/green]
|
66
|
-
console.rule(
|
66
|
+
console.print('[green][+][/green] ' + _('code_generation_success'))
|
67
|
+
console.rule(_('code_by_model', lang2str(lang), gpt.model.value))
|
67
68
|
console.print(Syntax(code=code, lexer=lang2str(lang)))
|
68
69
|
|
69
70
|
saved_filename = (
|
70
71
|
os.path.splitext(file)[0] + f'_by_{gpt.model.value}' + FILE_EXTENSIONS[lang]
|
71
72
|
)
|
72
73
|
with open(saved_filename, 'w') as f:
|
73
|
-
console.print(
|
74
|
-
f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
|
75
|
-
)
|
74
|
+
console.print('[green][+][/green] ' + _('code_saved', gpt.model.value, f.name))
|
76
75
|
f.write(code)
|
77
76
|
|
78
77
|
|
@@ -98,15 +97,13 @@ The user will provide a problem from a programming contest called AtCoder. This
|
|
98
97
|
|
99
98
|
You must not solve the problem. Please faithfully reproduce the variable names defined in the problem.
|
100
99
|
"""
|
101
|
-
with console.status(
|
100
|
+
with console.status(_('generating_template', lang2str(lang))):
|
102
101
|
reply = gpt.tell(md + propmpt)
|
103
102
|
code = get_code_from_gpt_output(reply)
|
104
103
|
|
105
104
|
savaed_filename = os.path.splitext(file)[0] + FILE_EXTENSIONS[lang]
|
106
105
|
with open(savaed_filename, 'x') as f:
|
107
|
-
console.print(
|
108
|
-
f'[green][+][/green] テンプレートファイルを作成 :{savaed_filename}'
|
109
|
-
)
|
106
|
+
console.print('[green][+][/green] ' + _('template_created', savaed_filename))
|
110
107
|
f.write(code)
|
111
108
|
|
112
109
|
|
@@ -128,7 +125,7 @@ def solve_problem(file: Filename, lang: Lang, model: Model) -> None:
|
|
128
125
|
file_without_ext = os.path.splitext(file)[0]
|
129
126
|
|
130
127
|
for i in range(1, 4):
|
131
|
-
with console.status(
|
128
|
+
with console.status(_('nth_code_generation', i, gpt.model.value)):
|
132
129
|
if i == 1:
|
133
130
|
test_report = ''
|
134
131
|
reply = gpt.tell(md)
|
@@ -137,7 +134,7 @@ def solve_problem(file: Filename, lang: Lang, model: Model) -> None:
|
|
137
134
|
{test_report}
|
138
135
|
Please provide an updated version of the code in {lang2str(lang)}."""
|
139
136
|
console.print(
|
140
|
-
|
137
|
+
'[green][+][/] ' + _('regenerating_with_prompt', gpt.model.value)
|
141
138
|
)
|
142
139
|
console.print(Panel(prompt))
|
143
140
|
reply = gpt.tell(prompt)
|
@@ -151,11 +148,11 @@ Please provide an updated version of the code in {lang2str(lang)}."""
|
|
151
148
|
+ FILE_EXTENSIONS[lang]
|
152
149
|
)
|
153
150
|
with open(saved_filename, 'w') as f:
|
154
|
-
console.print(
|
151
|
+
console.print('[green][+][/] ' + _('code_generation_success_file', f.name))
|
155
152
|
f.write(code)
|
156
153
|
|
157
154
|
with console.status(
|
158
|
-
|
155
|
+
_('testing_generated_code', gpt.model.value), spinner='circleHalves'
|
159
156
|
):
|
160
157
|
test = TestRunner(saved_filename, labeled_cases)
|
161
158
|
test_report, is_ac = render_result_for_GPT(test)
|
@@ -163,10 +160,10 @@ Please provide an updated version of the code in {lang2str(lang)}."""
|
|
163
160
|
console.print(create_renderable_test_info(test.info))
|
164
161
|
|
165
162
|
if is_ac:
|
166
|
-
console.print('[green][+][/]
|
163
|
+
console.print('[green][+][/] ' + _('test_success'))
|
167
164
|
break
|
168
165
|
else:
|
169
|
-
console.print('[red][-][/]
|
166
|
+
console.print('[red][-][/] ' + _('test_failed'))
|
170
167
|
|
171
168
|
with open(
|
172
169
|
'log_'
|
@@ -175,19 +172,17 @@ Please provide an updated version of the code in {lang2str(lang)}."""
|
|
175
172
|
+ FILE_EXTENSIONS[Lang.JSON],
|
176
173
|
'w',
|
177
174
|
) as f:
|
178
|
-
console.print(
|
179
|
-
f'[green][+][/] {gpt.model.value}の出力のログを保存しました:{f.name}'
|
180
|
-
)
|
175
|
+
console.print('[green][+][/] ' + _('log_saved', gpt.model.value, f.name))
|
181
176
|
f.write(json.dumps(gpt.messages, indent=2))
|
182
177
|
return
|
183
178
|
|
184
179
|
|
185
|
-
@click.command(short_help='
|
180
|
+
@click.command(short_help=_('cmd_generate'), help=_('cmd_generate'))
|
186
181
|
@add_file_selector('files', filetypes=[Lang.HTML])
|
187
|
-
@click.option('--lang', default='Python', help='
|
188
|
-
@click.option('--model', default=Model.GPT41_MINI.value, help='
|
189
|
-
@click.option('--without-test', is_flag=True, help='
|
190
|
-
@click.option('--template', is_flag=True, help='
|
182
|
+
@click.option('--lang', default='Python', help=_('opt_output_lang'))
|
183
|
+
@click.option('--model', default=Model.GPT41_MINI.value, help=_('opt_model'))
|
184
|
+
@click.option('--without-test', is_flag=True, help=_('opt_without_test'))
|
185
|
+
@click.option('--template', is_flag=True, help=_('opt_template'))
|
191
186
|
def generate(files, lang, model, without_test, template):
|
192
187
|
"""HTMLファイルからコード生成またはテンプレート出力を行います。"""
|
193
188
|
la = str2lang(lang)
|
atcdr/login.py
CHANGED
@@ -6,6 +6,7 @@ import webview
|
|
6
6
|
from requests import Session
|
7
7
|
from rich.console import Console
|
8
8
|
|
9
|
+
from atcdr.util.i18n import _
|
9
10
|
from atcdr.util.session import load_session, save_session, validate_session
|
10
11
|
|
11
12
|
ATCODER_LOGIN_URL = 'https://atcoder.jp/login'
|
@@ -14,17 +15,17 @@ ATCODER_HOME_URL = 'https://atcoder.jp/home'
|
|
14
15
|
console = Console()
|
15
16
|
|
16
17
|
|
17
|
-
@click.command(short_help='
|
18
|
+
@click.command(short_help=_('cmd_login'), help=_('cmd_login'))
|
18
19
|
def login() -> None:
|
19
20
|
"""AtCoderへログインします."""
|
20
21
|
session = load_session()
|
21
22
|
if validate_session(session):
|
22
|
-
console.print('[green][+][/]
|
23
|
+
console.print('[green][+][/] ' + _('already_logged_in'))
|
23
24
|
return
|
24
25
|
|
25
26
|
# Prompt in CLI
|
26
|
-
username = console.input('[cyan]
|
27
|
-
password = console.input('[cyan]
|
27
|
+
username = console.input('[cyan]' + _('username') + '[/]').strip()
|
28
|
+
password = console.input('[cyan]' + _('password') + '[/]').strip()
|
28
29
|
|
29
30
|
window = webview.create_window('AtCoder Login', ATCODER_LOGIN_URL, hidden=False)
|
30
31
|
|
@@ -36,16 +37,14 @@ def login() -> None:
|
|
36
37
|
window.evaluate_js(js_fill)
|
37
38
|
|
38
39
|
def poll_and_submit():
|
39
|
-
with console.status(
|
40
|
-
'キャプチャー認証を解決してください', spinner='circleHalves'
|
41
|
-
):
|
40
|
+
with console.status(_('solve_captcha'), spinner='circleHalves'):
|
42
41
|
while True:
|
43
42
|
try:
|
44
43
|
token = window.evaluate_js(
|
45
44
|
'document.querySelector(\'input[name=\\"cf-turnstile-response\\"]\').value'
|
46
45
|
)
|
47
46
|
if token:
|
48
|
-
console.print('[green][+][/]
|
47
|
+
console.print('[green][+][/] ' + _('logging_in'))
|
49
48
|
window.evaluate_js(
|
50
49
|
"document.getElementById('submit').click();"
|
51
50
|
)
|
@@ -55,7 +54,7 @@ def login() -> None:
|
|
55
54
|
|
56
55
|
time.sleep(0.5)
|
57
56
|
|
58
|
-
with console.status('
|
57
|
+
with console.status(_('waiting_login_result'), spinner='circleHalves'):
|
59
58
|
while True:
|
60
59
|
try:
|
61
60
|
current_url = window.get_current_url()
|
@@ -63,7 +62,7 @@ def login() -> None:
|
|
63
62
|
current_url = None
|
64
63
|
|
65
64
|
if current_url and current_url.startswith(ATCODER_HOME_URL):
|
66
|
-
console.print('[green][+][/]
|
65
|
+
console.print('[green][+][/] ' + _('login_success'))
|
67
66
|
|
68
67
|
session = Session()
|
69
68
|
session = move_cookies_from_webview_to_session(window, session)
|
@@ -82,7 +81,7 @@ def login() -> None:
|
|
82
81
|
err = ''
|
83
82
|
|
84
83
|
if err:
|
85
|
-
console.print(
|
84
|
+
console.print('[red][-][/] ' + _('error', err))
|
86
85
|
session = Session()
|
87
86
|
session = move_cookies_from_webview_to_session(window, session)
|
88
87
|
save_session(session)
|