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 CHANGED
@@ -1,165 +1,111 @@
1
- import os
2
1
  import re
3
2
  import time
4
- from typing import Callable, List, Union, cast
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, Diff, Problem
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 = load_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 _ in range(retry_attempts):
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] 再試行します。{problem}'
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] リダイレクトが発生しました。{problem}'
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] 問題が見つかりません。{problem}'
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] サーバーエラーが発生しました。{problem}'
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] {problem}に対応するHTMLファイルを取得できませんでした。'
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 generate_problem_directory(
88
- base_path: str, problems: List[Problem], gene_path: Callable[[str, Problem], str]
89
- ) -> None:
90
- downloader = Downloader()
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
- def parse_range(match: re.Match) -> List[int]:
118
- start, end = map(int, match.groups())
119
- start, end = min(start, end), max(start, end)
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
- def are_all_integers(args: Union[List[int], List[Diff]]) -> bool:
144
- return all(isinstance(arg, int) for arg in args)
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
- def are_all_diffs(args: Union[List[int], List[Diff]]) -> bool:
148
- return all(isinstance(arg, Diff) for arg in args)
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
- PRACTICE = '2. 特定の難易度の問題を集中的に練習したい'
154
- ONE_FILE = '3. 1問だけダウンロードする'
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='AtCoderの問題のHTMLファイルをダウンロードします',
104
+ message=_('download_atcoder_html'),
159
105
  qmark='',
160
106
  pointer='❯❯❯',
161
- choices=[CONTEST, PRACTICE, ONE_FILE, END],
162
- instruction='\n 十字キーで移動,[enter]で実行',
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
- difficulty = Diff(difficulty)
192
- except KeyError:
193
- raise ValueError('入力された難易度が認識できません')
194
- number_str = Prompt.ask(
195
- 'コンテスト番号または範囲を入力してください (例: 120..130)'
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
- 'コンテスト名を入力してください (例: abc012, abs, typical90)',
214
- )
215
-
216
- problems = Contest(name=name).problems(session=load_session())
217
-
218
- problem = q.select(
219
- message='どの問題をダウンロードしますか?',
220
- qmark='',
221
- pointer='❯❯❯',
222
- choices=[
223
- q.Choice(title=f'{problem.label:10} | {problem.url}', value=problem)
224
- for problem in problems
225
- ],
226
- instruction='\n 十字キーで移動,[enter]で実行',
227
- style=q.Style(
228
- [
229
- ('question', 'fg:#2196F3 bold'),
230
- ('answer', 'fg:#FFB300 bold'),
231
- ('pointer', 'fg:#FFB300 bold'),
232
- ('highlighted', 'fg:#FFB300 bold'),
233
- ('selected', 'fg:#FFB300 bold'),
234
- ]
235
- ),
236
- ).ask()
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
- @click.command(short_help='AtCoderの問題をダウンロード')
247
- @click.argument('first', nargs=1, type=str, required=False)
248
- @click.argument('second', nargs=1, type=str, required=False)
249
- def download(
250
- first: Union[str, None] = None,
251
- second: Union[str, None] = None,
252
- base_path: str = '.',
253
- ) -> None:
254
- """
255
- AtCoderの問題をダウンロードします
256
-
257
- download
258
- 対話形式でダウンロードを開始します。
259
-
260
- download abc012
261
- コンテスト abc012 の全問題をダウンロード
262
-
263
- download A 120
264
- 難易度Aの120番問題をダウンロード
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
- download 120
270
- ABCの120番問題をダウンロード
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
- if first is None:
273
- interactive_download()
274
- return
211
+ download abc{001..012} {A..C}
212
+ download {A..E} abc{001..012}
213
+ """
214
+ session = load_session()
275
215
 
276
- if second is None:
277
- try:
278
- first_args = convert_arg(str(first))
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
- if are_all_diffs(first_args):
289
- raise ValueError(
290
- """難易度だけでなく, 問題番号も指定してコマンドを実行してください.
291
- atcdr -d A 120 : A問題の120をダウンロードます
292
- 例 atcdr -d A 120..130 : A問題の120から130をダウンロードます
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
- if are_all_integers(first_args) and are_all_diffs(second_args):
299
- first_args_int = cast(List[int], first_args)
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(f'コード生成中 (by {gpt.model.value})'):
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(f'{gpt.model.value}による{lang2str(lang)}コード')
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(f'{lang2str(lang)}のテンプレートを生成しています...'):
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(f'{i}回目のコード生成中 (by {gpt.model.value})'):
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
- f'[green][+][/] 次のプロンプトを{gpt.model.value}に与え,再生成します'
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(f'[green][+][/] コードの生成に成功しました!:{f.name}')
151
+ console.print('[green][+][/] ' + _('code_generation_success_file', f.name))
155
152
  f.write(code)
156
153
 
157
154
  with console.status(
158
- f'{gpt.model.value}が生成したコードをテスト中', spinner='circleHalves'
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='使用するGPTモデル')
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='AtCoderへログイン')
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]ユーザー名: [/]').strip()
27
- password = console.input('[cyan]パスワード: [/]').strip()
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('ログインの結果の待機中...', spinner='circleHalves'):
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(f'[red][-][/] エラー: {err}')
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)