AtCoderStudyBooster 0.4.0__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 +32 -27
- 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 +3 -2
- 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.4.0.dist-info/METADATA +0 -261
- atcoderstudybooster-0.4.0.dist-info/RECORD +0 -21
- {atcoderstudybooster-0.4.0.dist-info → atcoderstudybooster-0.4.1.dist-info}/WHEEL +0 -0
- {atcoderstudybooster-0.4.0.dist-info → atcoderstudybooster-0.4.1.dist-info}/entry_points.txt +0 -0
atcdr/download.py
CHANGED
@@ -11,6 +11,7 @@ from rich import print
|
|
11
11
|
from rich.prompt import Prompt
|
12
12
|
|
13
13
|
from atcdr.util.filetype import FILE_EXTENSIONS, Lang
|
14
|
+
from atcdr.util.i18n import _, i18n
|
14
15
|
from atcdr.util.parse import ProblemHTML
|
15
16
|
from atcdr.util.problem import Contest, Problem
|
16
17
|
from atcdr.util.session import load_session
|
@@ -25,32 +26,37 @@ class Downloader:
|
|
25
26
|
retry_attempts = 3
|
26
27
|
retry_wait = 1 # 1 second
|
27
28
|
|
28
|
-
for
|
29
|
+
for attempt in range(retry_attempts):
|
29
30
|
response = session.get(problem.url)
|
30
31
|
if response.status_code == 200:
|
31
32
|
return ProblemHTML(response.text)
|
32
33
|
elif response.status_code == 429:
|
33
34
|
print(
|
34
|
-
f'[bold yellow][Error {response.status_code}][/bold yellow]
|
35
|
+
f'[bold yellow][Error {response.status_code}][/bold yellow] '
|
36
|
+
+ _('retry_problem', problem)
|
35
37
|
)
|
36
38
|
time.sleep(retry_wait)
|
37
39
|
elif 300 <= response.status_code < 400:
|
38
40
|
print(
|
39
|
-
f'[bold yellow][Error {response.status_code}][/bold yellow]
|
41
|
+
f'[bold yellow][Error {response.status_code}][/bold yellow] '
|
42
|
+
+ _('redirect_occurred', problem)
|
40
43
|
)
|
41
44
|
elif 400 <= response.status_code < 500:
|
42
45
|
print(
|
43
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
46
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
47
|
+
+ _('problem_not_found', problem)
|
44
48
|
)
|
45
49
|
break
|
46
50
|
elif 500 <= response.status_code < 600:
|
47
51
|
print(
|
48
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
52
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
53
|
+
+ _('server_error', problem)
|
49
54
|
)
|
50
55
|
break
|
51
56
|
else:
|
52
57
|
print(
|
53
|
-
f'[bold red][Error {response.status_code}][/bold red]
|
58
|
+
f'[bold red][Error {response.status_code}][/bold red] '
|
59
|
+
+ _('html_fetch_failed', problem)
|
54
60
|
)
|
55
61
|
break
|
56
62
|
return ProblemHTML('')
|
@@ -68,7 +74,7 @@ def save_problem(problem: Problem, path: Path, session: requests.Session) -> Non
|
|
68
74
|
problem_content = downloader.get(problem)
|
69
75
|
|
70
76
|
if not problem_content:
|
71
|
-
print(
|
77
|
+
print('[bold red][Error][/] ' + _('save_failed', problem))
|
72
78
|
return
|
73
79
|
|
74
80
|
# ディレクトリ作成(pathをそのまま使用)
|
@@ -80,26 +86,26 @@ def save_problem(problem: Problem, path: Path, session: requests.Session) -> Non
|
|
80
86
|
# HTMLファイル保存
|
81
87
|
html_path = path / (title + FILE_EXTENSIONS[Lang.HTML])
|
82
88
|
html_path.write_text(problem_content.html, encoding='utf-8')
|
83
|
-
print(
|
89
|
+
print('[bold green][+][/bold green] ' + _('file_saved', html_path))
|
84
90
|
|
85
91
|
# Markdownファイル保存
|
86
|
-
md = problem_content.make_problem_markdown(
|
92
|
+
md = problem_content.make_problem_markdown(i18n.language)
|
87
93
|
md_path = path / (title + FILE_EXTENSIONS[Lang.MARKDOWN])
|
88
94
|
md_path.write_text(md, encoding='utf-8')
|
89
|
-
print(
|
95
|
+
print('[bold green][+][/bold green] ' + _('file_saved', md_path))
|
90
96
|
|
91
97
|
|
92
98
|
def interactive_download(session) -> None:
|
93
|
-
CONTEST = '1.
|
94
|
-
ONE_FILE = '2.
|
95
|
-
END = '3.
|
99
|
+
CONTEST = '1. ' + _('solve_contest_problems')
|
100
|
+
ONE_FILE = '2. ' + _('download_one_problem')
|
101
|
+
END = '3. ' + _('exit')
|
96
102
|
|
97
103
|
choice = q.select(
|
98
|
-
message='
|
104
|
+
message=_('download_atcoder_html'),
|
99
105
|
qmark='',
|
100
106
|
pointer='❯❯❯',
|
101
107
|
choices=[CONTEST, ONE_FILE, END],
|
102
|
-
instruction='\n
|
108
|
+
instruction='\n ' + _('navigate_with_arrows'),
|
103
109
|
style=q.Style(
|
104
110
|
[
|
105
111
|
('question', 'fg:#2196F3 bold'),
|
@@ -112,7 +118,7 @@ def interactive_download(session) -> None:
|
|
112
118
|
).ask()
|
113
119
|
|
114
120
|
if choice == CONTEST:
|
115
|
-
name = Prompt.ask(
|
121
|
+
name = Prompt.ask(_('input_contest_name'))
|
116
122
|
try:
|
117
123
|
contest = Contest(name, session)
|
118
124
|
for problem in contest.problems:
|
@@ -121,18 +127,18 @@ def interactive_download(session) -> None:
|
|
121
127
|
print(f'[red][Error][/red] {e}')
|
122
128
|
|
123
129
|
elif choice == ONE_FILE:
|
124
|
-
name = Prompt.ask(
|
130
|
+
name = Prompt.ask(_('input_contest_name'))
|
125
131
|
try:
|
126
132
|
contest = Contest(name, session)
|
127
133
|
problem = q.select(
|
128
|
-
message='
|
134
|
+
message=_('which_problem_download'),
|
129
135
|
qmark='',
|
130
136
|
pointer='❯❯❯',
|
131
137
|
choices=[
|
132
138
|
q.Choice(title=f'{p.label:10} | {p.url}', value=p)
|
133
139
|
for p in contest.problems
|
134
140
|
],
|
135
|
-
instruction='\n
|
141
|
+
instruction='\n ' + _('navigate_with_arrows'),
|
136
142
|
style=q.Style(
|
137
143
|
[
|
138
144
|
('question', 'fg:#2196F3 bold'),
|
@@ -148,9 +154,9 @@ def interactive_download(session) -> None:
|
|
148
154
|
print(f'[red][Error][/red] {e}')
|
149
155
|
|
150
156
|
elif choice == END:
|
151
|
-
print('[bold red]
|
157
|
+
print('[bold red]' + _('exiting') + '[/]')
|
152
158
|
else:
|
153
|
-
print('[bold red]
|
159
|
+
print('[bold red]' + _('invalid_selection') + '[/]')
|
154
160
|
|
155
161
|
|
156
162
|
def plan_download(
|
@@ -179,7 +185,7 @@ def plan_download(
|
|
179
185
|
for problem in contest.problems
|
180
186
|
]
|
181
187
|
else:
|
182
|
-
raise ValueError('
|
188
|
+
raise ValueError(_('specify_contest_name'))
|
183
189
|
elif len(groups) == 2:
|
184
190
|
result = []
|
185
191
|
for i, j in product(groups[0], groups[1]):
|
@@ -195,16 +201,15 @@ def plan_download(
|
|
195
201
|
result.append((problem, Path(i) / j.name))
|
196
202
|
return result
|
197
203
|
else:
|
198
|
-
raise ValueError('
|
204
|
+
raise ValueError(_('invalid_download_args'))
|
199
205
|
|
200
206
|
|
201
|
-
@click.command(short_help='
|
207
|
+
@click.command(short_help=_('cmd_download'), help=_('cmd_download'))
|
202
208
|
@click.argument('args', nargs=-1)
|
203
209
|
def download(args: List[str]) -> None:
|
204
210
|
"""
|
205
|
-
|
206
|
-
|
207
|
-
download {A..E} abc{001..012}
|
211
|
+
download abc{001..012} {A..C}
|
212
|
+
download {A..E} abc{001..012}
|
208
213
|
"""
|
209
214
|
session = load_session()
|
210
215
|
|
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)
|
atcdr/logout.py
CHANGED
@@ -2,21 +2,22 @@ import rich_click as click
|
|
2
2
|
import webview
|
3
3
|
from rich import print
|
4
4
|
|
5
|
+
from atcdr.util.i18n import _
|
5
6
|
from atcdr.util.session import delete_session, load_session, validate_session
|
6
7
|
|
7
8
|
ATCODER_LOGIN_URL = 'https://atcoder.jp/login'
|
8
9
|
|
9
10
|
|
10
|
-
@click.command(short_help='
|
11
|
+
@click.command(short_help=_('cmd_logout'), help=_('cmd_logout'))
|
11
12
|
def logout() -> None:
|
12
13
|
"""AtCoderからログアウトします."""
|
13
14
|
session = load_session()
|
14
15
|
if not validate_session(session):
|
15
|
-
print('[red][-][/]
|
16
|
+
print('[red][-][/] ' + _('not_logged_in'))
|
16
17
|
return
|
17
18
|
|
18
19
|
delete_session()
|
19
|
-
print('[green][+][/]
|
20
|
+
print('[green][+][/] ' + _('logout_success'))
|
20
21
|
|
21
22
|
window = webview.create_window('AtCoder Logout', ATCODER_LOGIN_URL, hidden=True)
|
22
23
|
|
atcdr/markdown.py
CHANGED
@@ -6,6 +6,7 @@ from rich.markdown import Markdown
|
|
6
6
|
|
7
7
|
from atcdr.util.fileops import add_file_selector
|
8
8
|
from atcdr.util.filetype import FILE_EXTENSIONS, Lang
|
9
|
+
from atcdr.util.i18n import _, i18n
|
9
10
|
from atcdr.util.parse import ProblemHTML
|
10
11
|
|
11
12
|
|
@@ -19,7 +20,7 @@ def save_markdown(html_path: str, lang: str) -> None:
|
|
19
20
|
|
20
21
|
with open(md_path, 'w', encoding='utf-8') as f:
|
21
22
|
f.write(md)
|
22
|
-
console.print('[green][+][/green]
|
23
|
+
console.print('[green][+][/green] ' + _('markdown_created'))
|
23
24
|
|
24
25
|
|
25
26
|
def print_markdown(html_path: str, lang: str) -> None:
|
@@ -30,12 +31,15 @@ def print_markdown(html_path: str, lang: str) -> None:
|
|
30
31
|
console.print(Markdown(md))
|
31
32
|
|
32
33
|
|
33
|
-
@click.command(short_help='
|
34
|
+
@click.command(short_help=_('cmd_markdown'), help=_('cmd_markdown'))
|
34
35
|
@add_file_selector('files', filetypes=[Lang.HTML])
|
35
|
-
@click.option('--lang', default=
|
36
|
-
@click.option('--save', is_flag=True, help='
|
36
|
+
@click.option('--lang', default=None, help=_('opt_lang'))
|
37
|
+
@click.option('--save', is_flag=True, help=_('opt_save'))
|
37
38
|
def markdown(files, lang, save):
|
38
|
-
"""Markdown
|
39
|
+
"""問題をMarkdown形式で表示します"""
|
40
|
+
# langが指定されていない場合は現在のロケールを使用
|
41
|
+
if lang is None:
|
42
|
+
lang = i18n.language
|
39
43
|
for path in files:
|
40
44
|
if save:
|
41
45
|
save_markdown(path, lang)
|
atcdr/open.py
CHANGED
@@ -2,6 +2,7 @@ import webbrowser # noqa: I001
|
|
2
2
|
from rich.panel import Panel
|
3
3
|
from rich.console import Console
|
4
4
|
|
5
|
+
from atcdr.util.i18n import _
|
5
6
|
from atcdr.util.filetype import Lang
|
6
7
|
from atcdr.util.fileops import add_file_selector
|
7
8
|
import rich_click as click
|
@@ -16,7 +17,7 @@ def open_html(file: str) -> None:
|
|
16
17
|
except FileNotFoundError:
|
17
18
|
console.print(
|
18
19
|
Panel(
|
19
|
-
f"{file}' [red]
|
20
|
+
f"{file}' [red]" + _('not_found') + '[/]',
|
20
21
|
border_style='red',
|
21
22
|
)
|
22
23
|
)
|
@@ -27,20 +28,20 @@ def open_html(file: str) -> None:
|
|
27
28
|
webbrowser.open_new_tab(url)
|
28
29
|
console.print(
|
29
30
|
Panel(
|
30
|
-
|
31
|
+
'[green]' + _('url_opened') + f'[/] {url}',
|
31
32
|
border_style='green',
|
32
33
|
)
|
33
34
|
)
|
34
35
|
else:
|
35
36
|
console.print(
|
36
37
|
Panel(
|
37
|
-
f'{file} [yellow]
|
38
|
+
f'{file} [yellow]' + _('url_not_found_in') + '[/]',
|
38
39
|
border_style='yellow',
|
39
40
|
)
|
40
41
|
)
|
41
42
|
|
42
43
|
|
43
|
-
@click.command(short_help='
|
44
|
+
@click.command(short_help=_('cmd_open'), help=_('cmd_open'))
|
44
45
|
@add_file_selector('files', filetypes=[Lang.HTML])
|
45
46
|
def open_files(files):
|
46
47
|
"""指定したHTMLファイルをブラウザで開きます。"""
|
atcdr/submit.py
CHANGED
@@ -29,6 +29,7 @@ from atcdr.util.filetype import (
|
|
29
29
|
lang2str,
|
30
30
|
str2lang,
|
31
31
|
)
|
32
|
+
from atcdr.util.i18n import _
|
32
33
|
from atcdr.util.parse import ProblemHTML, get_submission_id
|
33
34
|
from atcdr.util.session import load_session, validate_session
|
34
35
|
|
@@ -61,14 +62,14 @@ def choose_langid_interactively(lang_dict: dict, lang: Lang) -> int:
|
|
61
62
|
options = [*filter(lambda option: option.lang == lang, options)]
|
62
63
|
|
63
64
|
langid = q.select(
|
64
|
-
message=
|
65
|
+
message=_('select_implementation', lang2str(lang)),
|
65
66
|
qmark='',
|
66
67
|
pointer='❯❯❯',
|
67
68
|
choices=[
|
68
69
|
q.Choice(title=f'{option.display_name}', value=option.id)
|
69
70
|
for option in options
|
70
71
|
],
|
71
|
-
instruction='\n
|
72
|
+
instruction='\n ' + _('navigate_with_arrows'),
|
72
73
|
style=q.Style(
|
73
74
|
[
|
74
75
|
('question', 'fg:#2196F3 bold'),
|
@@ -136,24 +137,24 @@ def post_source(source_path: str, url: str, session: requests.Session) -> Option
|
|
136
137
|
|
137
138
|
window.events.loaded += on_loaded
|
138
139
|
|
139
|
-
with Status('
|
140
|
+
with Status(_('solve_captcha'), spinner='circleHalves'):
|
140
141
|
webview.start(private_mode=False)
|
141
142
|
|
142
143
|
if 'submit' in api.url:
|
143
|
-
print('[red][-][/red]
|
144
|
+
print('[red][-][/red] ' + _('submission_failed'))
|
144
145
|
return None
|
145
146
|
elif 'submissions' in api.url:
|
146
147
|
submission_id = get_submission_id(api.html)
|
147
148
|
if not submission_id:
|
148
|
-
print('[red][-][/red]
|
149
|
+
print('[red][-][/red] ' + _('submission_id_not_found'))
|
149
150
|
return None
|
150
151
|
|
151
152
|
url = api.url.replace('/me', f'/{submission_id}')
|
152
|
-
print('[green][+][/green]
|
153
|
-
print(
|
153
|
+
print('[green][+][/green] ' + _('submission_success'))
|
154
|
+
print(_('submission_details', submission_id, url))
|
154
155
|
return url + '/status/json'
|
155
156
|
else:
|
156
|
-
print('[red][-][/red]
|
157
|
+
print('[red][-][/red] ' + _('submission_failed'))
|
157
158
|
return None
|
158
159
|
|
159
160
|
|
@@ -211,19 +212,19 @@ def print_status_submission(
|
|
211
212
|
BarColumn(),
|
212
213
|
)
|
213
214
|
|
214
|
-
with Status('
|
215
|
-
for
|
215
|
+
with Status(_('waiting_judge'), spinner='dots'):
|
216
|
+
for i in range(15):
|
216
217
|
time.sleep(1)
|
217
218
|
data = session.get(api_url).json()
|
218
219
|
status = parse_submission_status_json(data)
|
219
220
|
if status.total or status.current:
|
220
221
|
break
|
221
222
|
else:
|
222
|
-
print('[red][-][/]
|
223
|
+
print('[red][-][/] ' + _('judge_timeout'))
|
223
224
|
return
|
224
225
|
|
225
226
|
total = status.total or 0
|
226
|
-
task_id = progress.add_task(description='
|
227
|
+
task_id = progress.add_task(description=_('judging'), total=total)
|
227
228
|
|
228
229
|
test_info = TestInformation(
|
229
230
|
lang=detect_language(path),
|
@@ -248,24 +249,22 @@ def print_status_submission(
|
|
248
249
|
test_info.summary = status.status
|
249
250
|
test_info.results = [ResultStatus.AC] * total
|
250
251
|
|
251
|
-
progress.update(task_id, description='
|
252
|
+
progress.update(task_id, description=_('judge_completed'), completed=total)
|
252
253
|
live.update(create_renderable_test_info(test_info, progress))
|
253
254
|
|
254
255
|
|
255
256
|
def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
|
256
257
|
session = load_session()
|
257
258
|
if not validate_session(session):
|
258
|
-
print('[red][-][/]
|
259
|
+
print('[red][-][/] ' + _('not_logged_in'))
|
259
260
|
login()
|
260
261
|
if not validate_session(session):
|
261
|
-
print('[red][-][/]
|
262
|
+
print('[red][-][/] ' + _('login_failed'))
|
262
263
|
return
|
263
264
|
|
264
265
|
html_files = [file for file in os.listdir('.') if file.endswith('.html')]
|
265
266
|
if not html_files:
|
266
|
-
print(
|
267
|
-
'問題のファイルが見つかりません \n問題のファイルが存在するディレクトリーに移動してから実行してください'
|
268
|
-
)
|
267
|
+
print(_('problem_file_not_found'))
|
269
268
|
return
|
270
269
|
|
271
270
|
with open(html_files[0], 'r') as file:
|
@@ -279,7 +278,7 @@ def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
|
|
279
278
|
print(create_renderable_test_info(test.info))
|
280
279
|
|
281
280
|
if test.info.summary != ResultStatus.AC and not no_test:
|
282
|
-
print('[red][-][/]
|
281
|
+
print('[red][-][/] ' + _('sample_not_ac'))
|
283
282
|
return
|
284
283
|
|
285
284
|
api_status_link = post_source(path, url, session)
|
@@ -290,12 +289,10 @@ def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
|
|
290
289
|
print_status_submission(api_status_link, path, session)
|
291
290
|
|
292
291
|
|
293
|
-
@click.command(short_help='
|
292
|
+
@click.command(short_help=_('cmd_submit'), help=_('cmd_submit'))
|
294
293
|
@add_file_selector('files', filetypes=COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
|
295
|
-
@click.option('--no-test', is_flag=True, default=False, help='
|
296
|
-
@click.option(
|
297
|
-
'--no-feedback', is_flag=True, default=False, help='フィードバックをスキップ'
|
298
|
-
)
|
294
|
+
@click.option('--no-test', is_flag=True, default=False, help=_('opt_no_test'))
|
295
|
+
@click.option('--no-feedback', is_flag=True, default=False, help=_('opt_no_feedback'))
|
299
296
|
def submit(files, no_test, no_feedback):
|
300
297
|
"""指定したファイルをAtCoderへ提出します。"""
|
301
298
|
for path in files:
|