AtCoderStudyBooster 0.2__py3-none-any.whl → 0.3.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/generate.py CHANGED
@@ -3,104 +3,92 @@ import os
3
3
  import re
4
4
 
5
5
  from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.syntax import Syntax
6
8
 
7
- from atcdr.test import (
8
- LabeledTestCaseResult,
9
- ResultStatus,
10
- create_testcases_from_html,
11
- judge_code_from,
12
- render_results,
13
- )
9
+ from atcdr.test import ResultStatus, TestRunner, create_renderable_test_info
14
10
  from atcdr.util.execute import execute_files
15
11
  from atcdr.util.filetype import (
16
- FILE_EXTENSIONS,
17
- Filename,
18
- Lang,
19
- lang2str,
20
- str2lang,
12
+ FILE_EXTENSIONS,
13
+ Filename,
14
+ Lang,
15
+ lang2str,
16
+ str2lang,
21
17
  )
22
- from atcdr.util.gpt import ChatGPT, set_api_key
23
- from atcdr.util.problem import make_problem_markdown
18
+ from atcdr.util.gpt import ChatGPT, Model, set_api_key
19
+ from atcdr.util.parse import ProblemHTML
24
20
 
25
21
 
26
22
  def get_code_from_gpt_output(output: str) -> str:
27
- pattern = re.compile(r'```(?:\w+)?\s*(.*?)\s*```', re.DOTALL)
28
- match = pattern.search(output)
29
- return match.group(1) if match else ''
30
-
31
-
32
- def render_result_for_GPT(lresult: LabeledTestCaseResult) -> str:
33
- output = f'{lresult.label} of Test:\n'
34
- result = lresult.result
35
- testcase = lresult.testcase
36
-
37
- if result.passed == ResultStatus.AC:
38
- output += 'Accepted !! \n'
39
-
40
- elif result.passed == ResultStatus.WA:
41
- output += (
42
- f'Wrong Answer\n'
43
- f'Output:\n{result.output}\n'
44
- f'Expected Output:\n{testcase.output}\n'
45
- )
46
-
47
- elif result.passed == ResultStatus.RE:
48
- output += f'[RE] Runtime Error\n Output:\n{result.output}'
49
-
50
- elif result.passed == ResultStatus.TLE:
51
- output += '[TLE] Time Limit Exceeded\n'
52
-
53
- elif result.passed == ResultStatus.CE:
54
- output += f'[CE] Compile Error\n Output:\n{result.output}'
55
-
56
- elif result.passed == ResultStatus.MLE:
57
- output += '[ME] Memory Limit Exceeded\n'
58
-
59
- return output
60
-
61
-
62
- def generate_code(file: Filename, lang: Lang) -> None:
63
- console = Console()
64
- with open(file, 'r') as f:
65
- html_content = f.read()
66
- md = make_problem_markdown(html_content, 'en')
67
-
68
- if set_api_key() is None:
69
- return
70
- gpt = ChatGPT(
71
- 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)}.""",
72
- )
73
- with console.status(f'{gpt.model.value}がコードを生成しています...'):
74
- reply = gpt.tell(md)
75
-
76
- code = get_code_from_gpt_output(reply)
77
-
78
- saved_filename = (
79
- os.path.splitext(file)[0] + f'_by_{gpt.model.value}' + FILE_EXTENSIONS[lang]
80
- )
81
- with open(saved_filename, 'w') as f:
82
- console.print(
83
- f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
84
- )
85
- f.write(code)
86
-
87
- console.print(f'[info] AI利用にかかったAPIコスト: {gpt.sum_cost}')
23
+ pattern = re.compile(r'```(?:\w+)?\s*(.*?)\s*```', re.DOTALL)
24
+ match = pattern.search(output)
25
+ return match.group(1) if match else ''
26
+
27
+
28
+ def render_result_for_GPT(
29
+ test: TestRunner,
30
+ ) -> tuple[str, bool]:
31
+ results = list(test)
32
+
33
+ match test.info.summary:
34
+ case ResultStatus.AC:
35
+ return 'Accepted', True
36
+ case ResultStatus.CE:
37
+ return f'Compile Error \n {test.info.compiler_message}', False
38
+ case _:
39
+ message_for_gpt = ''.join(
40
+ f'\n{result.label} => {result.result.passed.value}\nInput :\n{result.testcase.input}\nOutput :\n{result.result.output}\nExpected :\n{result.testcase.output}\n'
41
+ if result.result.passed == ResultStatus.WA
42
+ else f'\n{result.label} => {result.result.passed.value}\nInput :\n{result.testcase.input}\nOutput :\n{result.result.output}\n'
43
+ for result in results
44
+ )
45
+ return message_for_gpt, False
46
+
47
+
48
+ def generate_code(file: Filename, lang: Lang, model: Model) -> None:
49
+ console = Console()
50
+ with open(file, 'r') as f:
51
+ html_content = f.read()
52
+ md = ProblemHTML(html_content).make_problem_markdown('en')
53
+
54
+ if set_api_key() is None:
55
+ return
56
+ gpt = ChatGPT(
57
+ 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)}.""",
58
+ model=model,
59
+ )
60
+ with console.status(f'コード生成中 (by {gpt.model.value})'):
61
+ reply = gpt.tell(md)
62
+
63
+ code = get_code_from_gpt_output(reply)
64
+ console.print('[green][+][/green] コードの生成に成功しました. ')
65
+ console.rule(f'{gpt.model.value}による{lang2str(lang)}コード')
66
+ console.print(Syntax(code=code, lexer=lang2str(lang)))
67
+
68
+ saved_filename = (
69
+ os.path.splitext(file)[0] + f'_by_{gpt.model.value}' + FILE_EXTENSIONS[lang]
70
+ )
71
+ with open(saved_filename, 'w') as f:
72
+ console.print(
73
+ f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
74
+ )
75
+ f.write(code)
88
76
 
89
77
 
90
78
  def generate_template(file: Filename, lang: Lang) -> None:
91
- console = Console()
92
- with open(file, 'r') as f:
93
- html_content = f.read()
94
- md = make_problem_markdown(html_content, 'en')
95
-
96
- if set_api_key() is None:
97
- return
98
- gpt = ChatGPT(
99
- system_prompt='You are a highly skilled programmer. Your role is to create a template code for competitive programming.',
100
- temperature=0.0,
101
- )
102
-
103
- propmpt = f"""
79
+ console = Console()
80
+ with open(file, 'r') as f:
81
+ html_content = f.read()
82
+ md = ProblemHTML(html_content).make_problem_markdown('en')
83
+
84
+ if set_api_key() is None:
85
+ return
86
+ gpt = ChatGPT(
87
+ system_prompt='You are a highly skilled programmer. Your role is to create a template code for competitive programming.',
88
+ temperature=0.0,
89
+ )
90
+
91
+ propmpt = f"""
104
92
  The user will provide a problem from a programming contest called AtCoder. This problem will include the Problem Statement, Constraints, Input, Output, Input Example, and Output Example. You should focus on the Constraints and Input sections to create the template in {lang2str(lang)}.
105
93
 
106
94
  - First, create the part of the code that handles input. Then, you should read ###Input Block and ###Constraints Block.
@@ -109,112 +97,115 @@ The user will provide a problem from a programming contest called AtCoder. This
109
97
 
110
98
  You must not solve the problem. Please faithfully reproduce the variable names defined in the problem.
111
99
  """
112
- with console.status(f'{lang2str(lang)}のテンプレートを生成しています...'):
113
- reply = gpt.tell(md + propmpt)
114
- code = get_code_from_gpt_output(reply)
115
-
116
- savaed_filename = os.path.splitext(file)[0] + FILE_EXTENSIONS[lang]
117
- with open(savaed_filename, 'x') as f:
118
- console.print(
119
- f'[green][+][/green] テンプレートファイルを作成 :{savaed_filename}'
120
- )
121
- f.write(code)
122
-
123
- console.print(f'[info] AI利用にかかったAPIコスト: {gpt.sum_cost}')
124
-
125
-
126
- def solve_problem(file: Filename, lang: Lang) -> None:
127
- console = Console()
128
- with open(file, 'r') as f:
129
- html_content = f.read()
130
- md = make_problem_markdown(html_content, 'en')
131
- labeled_cases = create_testcases_from_html(html_content)
132
-
133
- if set_api_key() is None:
134
- return
135
- gpt = ChatGPT(
136
- system_prompt=f"""You are a brilliant programmer. Your task is to solve an AtCoder problem. AtCoder is a platform that hosts programming competitions where participants write programs to solve algorithmic challenges.Please solve the problem in {lang2str(lang)}.""",
137
- )
138
-
139
- file_without_ext = os.path.splitext(file)[0]
140
-
141
- for i in range(1, 4):
142
- with console.status(f'{i}回目のコード生成 (by {gpt.model.value})...'):
143
- test_report = ''
144
- if i == 1:
145
- reply = gpt.tell(md)
146
- else:
147
- reply = gpt.tell(f"""The following is the test report for the code you provided:
148
- {test_report}
149
- Please provide an updated version of the code in {lang2str(lang)}.""")
150
-
151
- code = get_code_from_gpt_output(reply)
152
-
153
- saved_filename = (
154
- f'{i}_'
155
- + file_without_ext
156
- + f'_by_{gpt.model.value}'
157
- + FILE_EXTENSIONS[lang]
158
- )
159
- with open(saved_filename, 'w') as f:
160
- console.print(
161
- f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
162
- )
163
- f.write(code)
164
-
165
- labeled_results = judge_code_from(labeled_cases, saved_filename)
166
- test_report = '\n'.join(
167
- render_result_for_GPT(lresult) for lresult in labeled_results
168
- )
169
-
170
- console.rule(f'{i}回目のコード生成でのテスト結果')
171
- render_results(saved_filename, labeled_results)
172
-
173
- if all(
174
- labeled_result.result.passed == ResultStatus.AC
175
- for labeled_result in labeled_results
176
- ):
177
- console.print('[green]コードのテストに成功![/green]')
178
- break
179
-
180
- with open(
181
- 'log_'
182
- + file_without_ext
183
- + f'_by_{gpt.model.value}'
184
- + FILE_EXTENSIONS[Lang.JSON],
185
- 'w',
186
- ) as f:
187
- console.print(
188
- f'[green][+][/green] {gpt.model.value}の出力のログを保存しました:{f.name}'
189
- )
190
- f.write(json.dumps(gpt.messages, indent=2))
191
- console.print(f'AI利用にかかったAPIコスト:{gpt.sum_cost}')
192
- return
100
+ with console.status(f'{lang2str(lang)}のテンプレートを生成しています...'):
101
+ reply = gpt.tell(md + propmpt)
102
+ code = get_code_from_gpt_output(reply)
103
+
104
+ savaed_filename = os.path.splitext(file)[0] + FILE_EXTENSIONS[lang]
105
+ with open(savaed_filename, 'x') as f:
106
+ console.print(
107
+ f'[green][+][/green] テンプレートファイルを作成 :{savaed_filename}'
108
+ )
109
+ f.write(code)
110
+
111
+
112
+ def solve_problem(file: Filename, lang: Lang, model: Model) -> None:
113
+ console = Console()
114
+ with open(file, 'r') as f:
115
+ html = ProblemHTML(f.read())
116
+
117
+ md = html.make_problem_markdown('en')
118
+ labeled_cases = html.load_labeled_testcase()
119
+
120
+ if set_api_key() is None:
121
+ return
122
+ gpt = ChatGPT(
123
+ system_prompt=f"""You are a brilliant programmer. Your task is to solve an AtCoder problem. AtCoder is a platform that hosts programming competitions where participants write programs to solve algorithmic challenges.Please solve the problem in {lang2str(lang)}.""",
124
+ model=model,
125
+ )
126
+
127
+ file_without_ext = os.path.splitext(file)[0]
128
+
129
+ for i in range(1, 4):
130
+ with console.status(f'{i}回目のコード生成中 (by {gpt.model.value})'):
131
+ if i == 1:
132
+ test_report = ''
133
+ reply = gpt.tell(md)
134
+ else:
135
+ prompt = f"""The following is the test report for the code you provided:
136
+ {test_report}
137
+ Please provide an updated version of the code in {lang2str(lang)}."""
138
+ console.print(
139
+ f'[green][+][/] 次のプロンプトを{gpt.model.value}に与え,再生成します'
140
+ )
141
+ console.print(Panel(prompt))
142
+ reply = gpt.tell(prompt)
143
+
144
+ code = get_code_from_gpt_output(reply)
145
+
146
+ saved_filename = (
147
+ f'{i}_'
148
+ + file_without_ext
149
+ + f'_by_{gpt.model.value}'
150
+ + FILE_EXTENSIONS[lang]
151
+ )
152
+ with open(saved_filename, 'w') as f:
153
+ console.print(f'[green][+][/] コードの生成に成功しました!:{f.name}')
154
+ f.write(code)
155
+
156
+ with console.status(
157
+ f'{gpt.model.value}が生成したコードをテスト中', spinner='circleHalves'
158
+ ):
159
+ test = TestRunner(saved_filename, labeled_cases)
160
+ test_report, is_ac = render_result_for_GPT(test)
161
+
162
+ console.print(create_renderable_test_info(test.info))
163
+
164
+ if is_ac:
165
+ console.print('[green][+][/] コードのテストに成功!')
166
+ break
167
+ else:
168
+ console.print('[red][-][/] コードのテストに失敗!')
169
+
170
+ with open(
171
+ 'log_'
172
+ + file_without_ext
173
+ + f'_by_{gpt.model.value}'
174
+ + FILE_EXTENSIONS[Lang.JSON],
175
+ 'w',
176
+ ) as f:
177
+ console.print(
178
+ f'[green][+][/] {gpt.model.value}の出力のログを保存しました:{f.name}'
179
+ )
180
+ f.write(json.dumps(gpt.messages, indent=2))
181
+ return
193
182
 
194
183
 
195
184
  def generate(
196
- *source: str,
197
- lang: str = 'Python',
198
- without_test: bool = False,
199
- template: bool = False,
185
+ *source: str,
186
+ lang: str = 'Python',
187
+ model: str = Model.GPT41_MINI.value,
188
+ without_test: bool = False,
189
+ template: bool = False,
200
190
  ) -> None:
201
- la = str2lang(lang)
202
-
203
- if template:
204
- execute_files(
205
- *source,
206
- func=lambda file: generate_template(file, la),
207
- target_filetypes=[Lang.HTML],
208
- )
209
- elif without_test:
210
- execute_files(
211
- *source,
212
- func=lambda file: generate_code(file, la),
213
- target_filetypes=[Lang.HTML],
214
- )
215
- else:
216
- execute_files(
217
- *source,
218
- func=lambda file: solve_problem(file, la),
219
- target_filetypes=[Lang.HTML],
220
- )
191
+ la = str2lang(lang)
192
+ model_enum = Model(model)
193
+
194
+ if template:
195
+ execute_files(
196
+ *source,
197
+ func=lambda file: generate_template(file, la),
198
+ target_filetypes=[Lang.HTML],
199
+ )
200
+ elif without_test:
201
+ execute_files(
202
+ *source,
203
+ func=lambda file: generate_code(file, la, model_enum),
204
+ target_filetypes=[Lang.HTML],
205
+ )
206
+ else:
207
+ execute_files(
208
+ *source,
209
+ func=lambda file: solve_problem(file, la, model_enum),
210
+ target_filetypes=[Lang.HTML],
211
+ )
atcdr/login.py ADDED
@@ -0,0 +1,133 @@
1
+ import threading
2
+ import time
3
+
4
+ import webview
5
+ from requests import Session
6
+ from rich.console import Console
7
+
8
+ from atcdr.util.session import load_session, save_session, validate_session
9
+
10
+ ATCODER_LOGIN_URL = 'https://atcoder.jp/login'
11
+ ATCODER_HOME_URL = 'https://atcoder.jp/home'
12
+
13
+ console = Console()
14
+
15
+
16
+ def login() -> None:
17
+ session = load_session()
18
+ if validate_session(session):
19
+ console.print('[green][+][/] すでにログインしています. ')
20
+ return
21
+
22
+ # Prompt in CLI
23
+ username = console.input('[cyan]ユーザー名: [/]').strip()
24
+ password = console.input('[cyan]パスワード: [/]').strip()
25
+
26
+ window = webview.create_window('AtCoder Login', ATCODER_LOGIN_URL, hidden=False)
27
+
28
+ def on_start():
29
+ js_fill = f"""
30
+ document.getElementById('username').value = '{username}';
31
+ document.getElementById('password').value = '{password}';
32
+ """
33
+ window.evaluate_js(js_fill)
34
+
35
+ def poll_and_submit():
36
+ with console.status(
37
+ 'キャプチャー認証を解決してください', spinner='circleHalves'
38
+ ):
39
+ while True:
40
+ try:
41
+ token = window.evaluate_js(
42
+ 'document.querySelector(\'input[name=\\"cf-turnstile-response\\"]\').value'
43
+ )
44
+ if token:
45
+ console.print('[green][+][/] ログインします')
46
+ window.evaluate_js(
47
+ "document.getElementById('submit').click();"
48
+ )
49
+ break
50
+ except Exception:
51
+ pass
52
+
53
+ time.sleep(0.5)
54
+
55
+ with console.status('ログインの結果の待機中...', spinner='circleHalves'):
56
+ while True:
57
+ try:
58
+ current_url = window.get_current_url()
59
+ except Exception:
60
+ current_url = None
61
+
62
+ if current_url and current_url.startswith(ATCODER_HOME_URL):
63
+ console.print('[green][+][/] ログイン成功!')
64
+
65
+ session = Session()
66
+ session = move_cookies_from_webview_to_session(window, session)
67
+ save_session(session)
68
+ window.destroy()
69
+ break
70
+
71
+ try:
72
+ err = window.evaluate_js(
73
+ 'Array.from(document.querySelectorAll('
74
+ '\'div.alert.alert-danger[role="alert"]\'))'
75
+ ".map(e=>e.textContent.trim()).filter(t=>t).join(' ')"
76
+ )
77
+ err = err.replace('\n', '').replace('\r', '').replace('\t', '')
78
+ except Exception:
79
+ err = ''
80
+
81
+ if err:
82
+ console.print(f'[red][-][/] エラー: {err}')
83
+ session = Session()
84
+ session = move_cookies_from_webview_to_session(window, session)
85
+ save_session(session)
86
+ window.destroy()
87
+ return
88
+
89
+ time.sleep(0.5)
90
+
91
+ t = threading.Thread(target=poll_and_submit, daemon=True)
92
+ t.start()
93
+
94
+ webview.start(on_start, private_mode=False)
95
+
96
+
97
+ def move_cookies_from_webview_to_session(
98
+ window: webview.Window, session: Session
99
+ ) -> Session:
100
+ cookie_list = window.get_cookies()
101
+ for cookie_obj in cookie_list:
102
+ for cookie_name, morsel in cookie_obj.items():
103
+ # morselからデータを取得
104
+ value = morsel.value
105
+
106
+ domain = morsel.get('domain')
107
+ if domain is None:
108
+ domain = '.atcoder.jp'
109
+
110
+ path = morsel.get('path', '/')
111
+ secure = 'secure' in morsel
112
+
113
+ expires = None # __NSTaggedDateオブジェクトを回避
114
+
115
+ http_only = 'httponly' in morsel
116
+
117
+ # HttpOnlyをrestに含める
118
+ rest = {}
119
+ if http_only:
120
+ rest['HttpOnly'] = True
121
+
122
+ # セッションにクッキーを設定
123
+ session.cookies.set(
124
+ name=cookie_name,
125
+ value=value,
126
+ domain=domain,
127
+ path=path,
128
+ secure=secure,
129
+ expires=expires, # Noneを渡す
130
+ rest=rest,
131
+ )
132
+
133
+ return session
atcdr/logout.py ADDED
@@ -0,0 +1,24 @@
1
+ import webview
2
+ from rich import print
3
+
4
+ from atcdr.util.session import delete_session, load_session, validate_session
5
+
6
+ ATCODER_LOGIN_URL = 'https://atcoder.jp/login'
7
+
8
+
9
+ def logout() -> None:
10
+ session = load_session()
11
+ if not validate_session(session):
12
+ print('[red][-][/] ログインしていません.')
13
+ return
14
+
15
+ delete_session()
16
+ print('[green][+][/] ログアウトしました.')
17
+
18
+ window = webview.create_window('AtCoder Logout', ATCODER_LOGIN_URL, hidden=True)
19
+
20
+ def on_start():
21
+ window.clear_cookies()
22
+ window.destroy()
23
+
24
+ webview.start(on_start, private_mode=False)
atcdr/main.py CHANGED
@@ -5,36 +5,43 @@ from rich.traceback import install
5
5
 
6
6
  from atcdr.download import download
7
7
  from atcdr.generate import generate
8
+ from atcdr.login import login
9
+ from atcdr.logout import logout
8
10
  from atcdr.markdown import markdown
9
11
  from atcdr.open import open_files
12
+ from atcdr.submit import submit
10
13
  from atcdr.test import test
11
14
 
12
15
 
13
16
  def get_version() -> None:
14
- meta = metadata('AtCoderStudyBooster')
15
- print(meta['Name'], meta['Version'])
17
+ meta = metadata('AtCoderStudyBooster')
18
+ print(meta['Name'], meta['Version'])
16
19
 
17
20
 
18
21
  MAP_COMMANDS: dict = {
19
- 'test': test,
20
- 't': test,
21
- 'download': download,
22
- 'd': download,
23
- 'open': open_files,
24
- 'o': open_files,
25
- 'generate': generate,
26
- 'g': generate,
27
- 'markdown': markdown,
28
- 'md': markdown,
29
- '--version': get_version,
30
- '-v': get_version,
22
+ 'test': test,
23
+ 't': test,
24
+ 'download': download,
25
+ 'd': download,
26
+ 'open': open_files,
27
+ 'o': open_files,
28
+ 'generate': generate,
29
+ 'g': generate,
30
+ 'markdown': markdown,
31
+ 'md': markdown,
32
+ 'login': login,
33
+ 'logout': logout,
34
+ 'submit': submit,
35
+ 's': submit,
36
+ '--version': get_version,
37
+ '-v': get_version,
31
38
  }
32
39
 
33
40
 
34
41
  def main():
35
- install()
36
- fire.Fire(MAP_COMMANDS)
42
+ install()
43
+ fire.Fire(MAP_COMMANDS)
37
44
 
38
45
 
39
46
  if __name__ == '__main__':
40
- main()
47
+ main()