AtCoderStudyBooster 0.1.0__tar.gz
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.
- atcoderstudybooster-0.1.0/.gitignore +48 -0
- atcoderstudybooster-0.1.0/.images/demo1.png +0 -0
- atcoderstudybooster-0.1.0/.pre-commit-config.yaml +23 -0
- atcoderstudybooster-0.1.0/.python-version +1 -0
- atcoderstudybooster-0.1.0/.vscode/extensions.json +5 -0
- atcoderstudybooster-0.1.0/.vscode/setting.json +10 -0
- atcoderstudybooster-0.1.0/.vscode/tasks.json +18 -0
- atcoderstudybooster-0.1.0/PKG-INFO +94 -0
- atcoderstudybooster-0.1.0/README.md +76 -0
- atcoderstudybooster-0.1.0/atcdr/__init__.py +0 -0
- atcoderstudybooster-0.1.0/atcdr/download.py +266 -0
- atcoderstudybooster-0.1.0/atcdr/generate.py +167 -0
- atcoderstudybooster-0.1.0/atcdr/main.py +35 -0
- atcoderstudybooster-0.1.0/atcdr/open.py +36 -0
- atcoderstudybooster-0.1.0/atcdr/test.py +276 -0
- atcoderstudybooster-0.1.0/atcdr/util/__init__.py +0 -0
- atcoderstudybooster-0.1.0/atcdr/util/cost.py +120 -0
- atcoderstudybooster-0.1.0/atcdr/util/filename.py +137 -0
- atcoderstudybooster-0.1.0/atcdr/util/gpt.py +112 -0
- atcoderstudybooster-0.1.0/atcdr/util/problem.py +87 -0
- atcoderstudybooster-0.1.0/pyproject.toml +51 -0
- atcoderstudybooster-0.1.0/requirements-dev.lock +112 -0
- atcoderstudybooster-0.1.0/requirements.lock +84 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# Pythonのバイトコードファイル
|
2
|
+
__pycache__/
|
3
|
+
*.py[cod]
|
4
|
+
*.py[oc]
|
5
|
+
|
6
|
+
# ビルドディレクトリ
|
7
|
+
build/
|
8
|
+
dist/
|
9
|
+
wheels/
|
10
|
+
*.egg-info/
|
11
|
+
|
12
|
+
# Python仮想環境
|
13
|
+
.venv
|
14
|
+
venv/
|
15
|
+
env/
|
16
|
+
*.venv/
|
17
|
+
|
18
|
+
# コンパイルされたファイル
|
19
|
+
*.so
|
20
|
+
*.dylib
|
21
|
+
*.dll
|
22
|
+
|
23
|
+
# Jupyter Notebook
|
24
|
+
.ipynb_checkpoints/
|
25
|
+
|
26
|
+
# 環境依存のファイル
|
27
|
+
*.env
|
28
|
+
.env
|
29
|
+
|
30
|
+
# IDE/エディタの設定ファイル
|
31
|
+
.idea/
|
32
|
+
*.swp
|
33
|
+
*.swo
|
34
|
+
|
35
|
+
# 一時ファイル
|
36
|
+
*.log
|
37
|
+
*.tmp
|
38
|
+
*.bak
|
39
|
+
|
40
|
+
# デバッグファイル
|
41
|
+
*.pdb
|
42
|
+
|
43
|
+
# OS固有の隠しファイル
|
44
|
+
.DS_Store
|
45
|
+
Thumbs.db
|
46
|
+
|
47
|
+
# キャッシュファイル
|
48
|
+
.*_cache
|
Binary file
|
@@ -0,0 +1,23 @@
|
|
1
|
+
repos:
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
3
|
+
rev: v4.6.0
|
4
|
+
hooks:
|
5
|
+
- id: trailing-whitespace
|
6
|
+
- id: end-of-file-fixer
|
7
|
+
|
8
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
9
|
+
# Ruff version.
|
10
|
+
rev: v0.5.7
|
11
|
+
hooks:
|
12
|
+
# Run the linter.
|
13
|
+
- id: ruff
|
14
|
+
args: [--fix]
|
15
|
+
# Run the formatter.
|
16
|
+
- id: ruff-format
|
17
|
+
|
18
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
19
|
+
rev: v1.11.1
|
20
|
+
hooks:
|
21
|
+
- id: mypy
|
22
|
+
additional_dependencies: [types-requests]
|
23
|
+
args: [--ignore-missing-imports]
|
@@ -0,0 +1 @@
|
|
1
|
+
3.12.4
|
@@ -0,0 +1,94 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: AtCoderStudyBooster
|
3
|
+
Version: 0.1.0
|
4
|
+
Summary: A tool to download and manage AtCoder problems.
|
5
|
+
Author-email: yuta6 <46110512+yuta6@users.noreply.github.com>
|
6
|
+
Requires-Python: >=3.8
|
7
|
+
Requires-Dist: beautifulsoup4
|
8
|
+
Requires-Dist: colorama
|
9
|
+
Requires-Dist: fire
|
10
|
+
Requires-Dist: markdownify>=0.13.1
|
11
|
+
Requires-Dist: requests
|
12
|
+
Requires-Dist: tiktoken
|
13
|
+
Requires-Dist: types-beautifulsoup4>=4.12.0.20240511
|
14
|
+
Requires-Dist: types-colorama>=0.4.15.20240311
|
15
|
+
Requires-Dist: types-requests>=2.32.0.20240712
|
16
|
+
Requires-Dist: yfinance
|
17
|
+
Description-Content-Type: text/markdown
|
18
|
+
|
19
|
+
# AtCoderStudyBooster
|
20
|
+
|
21
|
+
## 概要
|
22
|
+
|
23
|
+
AtCoderStudyBoosterはAtCoderの学習を加速させるためのツールです。問題をローカルにダウンロードし、テスト、解答の作成をサポートするツールです。Pythonが入っていることが必須です。Pythonが入っている環境なら、`pip install AtCoderStudyBooster`でインストールできます。
|
24
|
+
|
25
|
+
このツールは以下のプロジェクトに強く影響を受けています。
|
26
|
+
[online-judge-tools](https://github.com/online-judge-tools)
|
27
|
+
[atcoder-cli](https://github.com/Tatamo/atcoder-cli)
|
28
|
+
これらとの違いですが、本ツールはAtCoderでのコンテストでの利用は想定しておらず、初心者の学習のサポートのみを意識しています。そのため、現時点で提出機能は備えていません。また, Chat GPT APIによる解答の作成サポート機能を備えています。
|
29
|
+
|
30
|
+
## 利用ケース
|
31
|
+
|
32
|
+
### B問題の練習したい場合
|
33
|
+
|
34
|
+
ABCコンテストの223から226のB問題だけを集中的に練習したい場合、次のコマンドを実行します。
|
35
|
+
|
36
|
+
```sh
|
37
|
+
atcdr download B 223..226
|
38
|
+
```
|
39
|
+
|
40
|
+
コマンドを実行すると,次のようなフォルダーを作成して、各々のフォルダーに問題をダウンロードします。
|
41
|
+
|
42
|
+
```css
|
43
|
+
B
|
44
|
+
├── 223
|
45
|
+
│ ├── StringShifting.html
|
46
|
+
│ └── StringShifting.md
|
47
|
+
├── 224
|
48
|
+
│ ├── Mongeness.html
|
49
|
+
│ └── Mongeness.md
|
50
|
+
├── 225
|
51
|
+
│ ├── StarorNot.html
|
52
|
+
│ └── StarorNot.md
|
53
|
+
└── 226
|
54
|
+
├── CountingArrays.html
|
55
|
+
└── CountingArrays.md
|
56
|
+
```
|
57
|
+
|
58
|
+
MarkdownファイルあるいはHTMLファイルをVS CodeのHTML Preview, Markdown Previewで開くと問題を確認できます。VS Codeで開くと左側にテキストエディターを表示して、右側で問題をみながら問題に取り組めます。
|
59
|
+
|
60
|
+

|
61
|
+
|
62
|
+
### サンプルをローカルでテストする
|
63
|
+
|
64
|
+
問題をダウンロードしたフォルダーに移動します。
|
65
|
+
|
66
|
+
```sh
|
67
|
+
cd B/224
|
68
|
+
```
|
69
|
+
|
70
|
+
移動したフォルダーで解答ファイルを作成後を実行すると, 自動的にテストします。
|
71
|
+
|
72
|
+
```sh
|
73
|
+
▷ ~/.../B/224
|
74
|
+
atcdr t
|
75
|
+
```
|
76
|
+
|
77
|
+
```sh
|
78
|
+
solution.pyをテストします。
|
79
|
+
--------------------
|
80
|
+
|
81
|
+
Sample 1 of Test:
|
82
|
+
✓ Accepted !! Time: 24 ms
|
83
|
+
|
84
|
+
Sample 2 of Test:
|
85
|
+
✓ Accepted !! Time: 15 ms
|
86
|
+
```
|
87
|
+
|
88
|
+
と実行すると作成したソースコードをテストして、HTMLに書かれているテストケースを読み込んで実行し, Passするかを判定します。
|
89
|
+
|
90
|
+
## 解答生成機能generateコマンドに関する注意点
|
91
|
+
|
92
|
+
本ツールにはChatGPT APIを利用したコード生成機能があります。[AtCoder生成AI対策ルール](https://info.atcoder.jp/entry/llm-abc-rules-ja?_gl=1*1axgs02*_ga*ODc0NDAyNjA4LjE3MTk1ODEyNDA.*_ga_RC512FD18N*MTcyMzMxNDA1Ni43NC4xLjE3MjMzMTY1NjUuMC4wLjA.)によるとAtCoder Beginner Contestにおいてに問題文を生成AIに直接与えることは禁止されています。ただし、このルールは過去問を練習している際には適用されません。
|
93
|
+
|
94
|
+
現時点で本ツールにはログイン機能がないため、コンテスト中の問題に対して`download`コマンドは利用して問題をダウンロードすることはできません。`generate`コマンドは`download`コマンドに依存しており、ダウンロードした問題のHTMLファイルをパースしてGPTに解釈しやすいmarkdownを与えることで実現しています。したがって、このコマンドがAtCoder Beginner Contest中に[AtCoder生成AI対策ルール](https://info.atcoder.jp/entry/llm-abc-rules-ja?_gl=1*1axgs02*_ga*ODc0NDAyNjA4LjE3MTk1ODEyNDA.*_ga_RC512FD18N*MTcyMzMxNDA1Ni43NC4xLjE3MjMzMTY1NjUuMC4wLjA.)に抵触することはありません。
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# AtCoderStudyBooster
|
2
|
+
|
3
|
+
## 概要
|
4
|
+
|
5
|
+
AtCoderStudyBoosterはAtCoderの学習を加速させるためのツールです。問題をローカルにダウンロードし、テスト、解答の作成をサポートするツールです。Pythonが入っていることが必須です。Pythonが入っている環境なら、`pip install AtCoderStudyBooster`でインストールできます。
|
6
|
+
|
7
|
+
このツールは以下のプロジェクトに強く影響を受けています。
|
8
|
+
[online-judge-tools](https://github.com/online-judge-tools)
|
9
|
+
[atcoder-cli](https://github.com/Tatamo/atcoder-cli)
|
10
|
+
これらとの違いですが、本ツールはAtCoderでのコンテストでの利用は想定しておらず、初心者の学習のサポートのみを意識しています。そのため、現時点で提出機能は備えていません。また, Chat GPT APIによる解答の作成サポート機能を備えています。
|
11
|
+
|
12
|
+
## 利用ケース
|
13
|
+
|
14
|
+
### B問題の練習したい場合
|
15
|
+
|
16
|
+
ABCコンテストの223から226のB問題だけを集中的に練習したい場合、次のコマンドを実行します。
|
17
|
+
|
18
|
+
```sh
|
19
|
+
atcdr download B 223..226
|
20
|
+
```
|
21
|
+
|
22
|
+
コマンドを実行すると,次のようなフォルダーを作成して、各々のフォルダーに問題をダウンロードします。
|
23
|
+
|
24
|
+
```css
|
25
|
+
B
|
26
|
+
├── 223
|
27
|
+
│ ├── StringShifting.html
|
28
|
+
│ └── StringShifting.md
|
29
|
+
├── 224
|
30
|
+
│ ├── Mongeness.html
|
31
|
+
│ └── Mongeness.md
|
32
|
+
├── 225
|
33
|
+
│ ├── StarorNot.html
|
34
|
+
│ └── StarorNot.md
|
35
|
+
└── 226
|
36
|
+
├── CountingArrays.html
|
37
|
+
└── CountingArrays.md
|
38
|
+
```
|
39
|
+
|
40
|
+
MarkdownファイルあるいはHTMLファイルをVS CodeのHTML Preview, Markdown Previewで開くと問題を確認できます。VS Codeで開くと左側にテキストエディターを表示して、右側で問題をみながら問題に取り組めます。
|
41
|
+
|
42
|
+

|
43
|
+
|
44
|
+
### サンプルをローカルでテストする
|
45
|
+
|
46
|
+
問題をダウンロードしたフォルダーに移動します。
|
47
|
+
|
48
|
+
```sh
|
49
|
+
cd B/224
|
50
|
+
```
|
51
|
+
|
52
|
+
移動したフォルダーで解答ファイルを作成後を実行すると, 自動的にテストします。
|
53
|
+
|
54
|
+
```sh
|
55
|
+
▷ ~/.../B/224
|
56
|
+
atcdr t
|
57
|
+
```
|
58
|
+
|
59
|
+
```sh
|
60
|
+
solution.pyをテストします。
|
61
|
+
--------------------
|
62
|
+
|
63
|
+
Sample 1 of Test:
|
64
|
+
✓ Accepted !! Time: 24 ms
|
65
|
+
|
66
|
+
Sample 2 of Test:
|
67
|
+
✓ Accepted !! Time: 15 ms
|
68
|
+
```
|
69
|
+
|
70
|
+
と実行すると作成したソースコードをテストして、HTMLに書かれているテストケースを読み込んで実行し, Passするかを判定します。
|
71
|
+
|
72
|
+
## 解答生成機能generateコマンドに関する注意点
|
73
|
+
|
74
|
+
本ツールにはChatGPT APIを利用したコード生成機能があります。[AtCoder生成AI対策ルール](https://info.atcoder.jp/entry/llm-abc-rules-ja?_gl=1*1axgs02*_ga*ODc0NDAyNjA4LjE3MTk1ODEyNDA.*_ga_RC512FD18N*MTcyMzMxNDA1Ni43NC4xLjE3MjMzMTY1NjUuMC4wLjA.)によるとAtCoder Beginner Contestにおいてに問題文を生成AIに直接与えることは禁止されています。ただし、このルールは過去問を練習している際には適用されません。
|
75
|
+
|
76
|
+
現時点で本ツールにはログイン機能がないため、コンテスト中の問題に対して`download`コマンドは利用して問題をダウンロードすることはできません。`generate`コマンドは`download`コマンドに依存しており、ダウンロードした問題のHTMLファイルをパースしてGPTに解釈しやすいmarkdownを与えることで実現しています。したがって、このコマンドがAtCoder Beginner Contest中に[AtCoder生成AI対策ルール](https://info.atcoder.jp/entry/llm-abc-rules-ja?_gl=1*1axgs02*_ga*ODc0NDAyNjA4LjE3MTk1ODEyNDA.*_ga_RC512FD18N*MTcyMzMxNDA1Ni43NC4xLjE3MjMzMTY1NjUuMC4wLjA.)に抵触することはありません。
|
File without changes
|
@@ -0,0 +1,266 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
import time
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from enum import Enum
|
6
|
+
from typing import Callable, List, Match, Optional, Union, cast
|
7
|
+
|
8
|
+
import requests
|
9
|
+
|
10
|
+
from atcdr.util.problem import make_problem_markdown
|
11
|
+
|
12
|
+
|
13
|
+
class Diff(Enum):
|
14
|
+
A = 'A'
|
15
|
+
B = 'B'
|
16
|
+
C = 'C'
|
17
|
+
D = 'D'
|
18
|
+
E = 'E'
|
19
|
+
F = 'F'
|
20
|
+
G = 'G'
|
21
|
+
|
22
|
+
|
23
|
+
@dataclass
|
24
|
+
class Problem:
|
25
|
+
number: int
|
26
|
+
difficulty: Diff
|
27
|
+
|
28
|
+
|
29
|
+
def get_problem_html(problem: Problem) -> Optional[str]:
|
30
|
+
url = f'https://atcoder.jp/contests/abc{problem.number}/tasks/abc{problem.number}_{problem.difficulty.value.lower()}'
|
31
|
+
response = requests.get(url)
|
32
|
+
retry_attempts = 3
|
33
|
+
retry_wait = 1 # 1 second
|
34
|
+
|
35
|
+
for _ in range(retry_attempts):
|
36
|
+
response = requests.get(url)
|
37
|
+
if response.status_code == 200:
|
38
|
+
return response.text
|
39
|
+
elif response.status_code == 429:
|
40
|
+
print(
|
41
|
+
f'[Error{response.status_code}] 再試行します. abc{problem.number} {problem.difficulty.value}'
|
42
|
+
)
|
43
|
+
time.sleep(retry_wait)
|
44
|
+
elif 300 <= response.status_code < 400:
|
45
|
+
print(
|
46
|
+
f'[Erroe{response.status_code}] リダイレクトが発生しました。abc{problem.number} {problem.difficulty.value}'
|
47
|
+
)
|
48
|
+
elif 400 <= response.status_code < 500:
|
49
|
+
print(
|
50
|
+
f'[Error{response.status_code}] 問題が見つかりません。abc{problem.number} {problem.difficulty.value}'
|
51
|
+
)
|
52
|
+
break
|
53
|
+
elif 500 <= response.status_code < 600:
|
54
|
+
print(
|
55
|
+
f'[Error{response.status_code}] サーバーエラーが発生しました。abc{problem.number} {problem.difficulty.value}'
|
56
|
+
)
|
57
|
+
break
|
58
|
+
else:
|
59
|
+
print(
|
60
|
+
f'[Error{response.status_code}] abc{problem.number} {problem.difficulty.value}に対応するHTMLファイルを取得できませんでした。'
|
61
|
+
)
|
62
|
+
break
|
63
|
+
return None
|
64
|
+
|
65
|
+
|
66
|
+
def repair_html(html: str) -> str:
|
67
|
+
html = html.replace('//img.atcoder.jp', 'https://img.atcoder.jp')
|
68
|
+
html = html.replace(
|
69
|
+
'<meta http-equiv="Content-Language" content="en">',
|
70
|
+
'<meta http-equiv="Content-Language" content="ja">',
|
71
|
+
)
|
72
|
+
html = html.replace('LANG = "en"', 'LANG="ja"')
|
73
|
+
return html
|
74
|
+
|
75
|
+
|
76
|
+
def get_title_from_html(html: str) -> Optional[str]:
|
77
|
+
title_match: Optional[Match[str]] = re.search(
|
78
|
+
r'<title>(?:.*?-\s*)?([^<]*)</title>', html, re.IGNORECASE | re.DOTALL
|
79
|
+
)
|
80
|
+
if title_match:
|
81
|
+
title: str = title_match.group(1).replace(' ', '')
|
82
|
+
title = re.sub(r'[\\/*?:"<>| ]', '', title)
|
83
|
+
return title
|
84
|
+
return None
|
85
|
+
|
86
|
+
|
87
|
+
def save_file(file_path: str, html: str) -> None:
|
88
|
+
with open(file_path, 'w', encoding='utf-8') as file:
|
89
|
+
file.write(html)
|
90
|
+
print(f'[+] ファイルを保存しました :{file_path}')
|
91
|
+
|
92
|
+
|
93
|
+
def mkdir(path: str) -> None:
|
94
|
+
if not os.path.exists(path):
|
95
|
+
os.makedirs(path)
|
96
|
+
print(f'[+] フォルダー: {path} を作成しました')
|
97
|
+
|
98
|
+
|
99
|
+
class GenerateMode:
|
100
|
+
@staticmethod
|
101
|
+
def gene_path_on_diff(base: str, number: int, diff: Diff) -> str:
|
102
|
+
return os.path.join(base, diff.name, str(number))
|
103
|
+
|
104
|
+
@staticmethod
|
105
|
+
def gene_path_on_num(base: str, number: int, diff: Diff) -> str:
|
106
|
+
return os.path.join(base, str(number), diff.name)
|
107
|
+
|
108
|
+
|
109
|
+
def generate_problem_directory(
|
110
|
+
base_path: str, problems: List[Problem], gene_path: Callable[[str, int, Diff], str]
|
111
|
+
) -> None:
|
112
|
+
for problem in problems:
|
113
|
+
dir_path = gene_path(base_path, problem.number, problem.difficulty)
|
114
|
+
|
115
|
+
html = get_problem_html(problem)
|
116
|
+
if html is None:
|
117
|
+
continue
|
118
|
+
|
119
|
+
title = get_title_from_html(html)
|
120
|
+
if title is None:
|
121
|
+
print('[Error] タイトルが取得できませんでした')
|
122
|
+
title = f'problem{problem.number}{problem.difficulty.value}'
|
123
|
+
|
124
|
+
mkdir(dir_path)
|
125
|
+
repaired_html = repair_html(html)
|
126
|
+
|
127
|
+
html_path = os.path.join(dir_path, f'{title}.html')
|
128
|
+
save_file(html_path, repaired_html)
|
129
|
+
md = make_problem_markdown(html, 'ja')
|
130
|
+
save_file(os.path.join(dir_path, f'{title}.md'), md)
|
131
|
+
|
132
|
+
|
133
|
+
def parse_range(range_str: str) -> List[int]:
|
134
|
+
match = re.match(r'^(\d+)\.\.(\d+)$', range_str)
|
135
|
+
if match:
|
136
|
+
start, end = map(int, match.groups())
|
137
|
+
return list(range(start, end + 1))
|
138
|
+
else:
|
139
|
+
raise ValueError('Invalid range format')
|
140
|
+
|
141
|
+
|
142
|
+
def parse_diff_range(range_str: str) -> List[Diff]:
|
143
|
+
match = re.match(r'^([A-F])\.\.([A-F])$', range_str)
|
144
|
+
if match:
|
145
|
+
start, end = match.groups()
|
146
|
+
start_index = ord(start) - ord('A')
|
147
|
+
end_index = ord(end) - ord('A')
|
148
|
+
if start_index <= end_index:
|
149
|
+
return [Diff(chr(i + ord('A'))) for i in range(start_index, end_index + 1)]
|
150
|
+
raise ValueError('A..C の形式になっていません')
|
151
|
+
|
152
|
+
|
153
|
+
def convert_arg(arg: Union[str, int]) -> Union[List[int], List[Diff]]:
|
154
|
+
if isinstance(arg, int):
|
155
|
+
return [arg]
|
156
|
+
elif isinstance(arg, str):
|
157
|
+
if arg.isdigit():
|
158
|
+
return [int(arg)]
|
159
|
+
elif arg in Diff.__members__:
|
160
|
+
return [Diff[arg]]
|
161
|
+
elif re.match(r'^\d+\.\.\d+$', arg):
|
162
|
+
return parse_range(arg)
|
163
|
+
elif re.match(r'^[A-F]\.\.[A-F]$', arg):
|
164
|
+
return parse_diff_range(arg)
|
165
|
+
raise ValueError(f'{arg}は認識できません')
|
166
|
+
|
167
|
+
|
168
|
+
def are_all_integers(args: Union[List[int], List[Diff]]) -> bool:
|
169
|
+
return all(isinstance(arg, int) for arg in args)
|
170
|
+
|
171
|
+
|
172
|
+
def are_all_diffs(args: Union[List[int], List[Diff]]) -> bool:
|
173
|
+
return all(isinstance(arg, Diff) for arg in args)
|
174
|
+
|
175
|
+
|
176
|
+
def download(
|
177
|
+
first: Union[str, int, None] = None,
|
178
|
+
second: Union[str, int, None] = None,
|
179
|
+
base_path: str = '.',
|
180
|
+
) -> None:
|
181
|
+
if first is None:
|
182
|
+
main()
|
183
|
+
return
|
184
|
+
|
185
|
+
first_args = convert_arg(str(first))
|
186
|
+
if second is None:
|
187
|
+
if isinstance(first, Diff):
|
188
|
+
raise ValueError(
|
189
|
+
"""難易度だけでなく, 問題番号も指定してコマンドを実行してください.
|
190
|
+
例 atcdr -d A 120 : A問題の120をダウンロードます
|
191
|
+
例 atcdr -d A 120..130 : A問題の120から130をダウンロードます
|
192
|
+
"""
|
193
|
+
)
|
194
|
+
second_args: Union[List[int], List[Diff]] = list(Diff)
|
195
|
+
else:
|
196
|
+
second_args = convert_arg(str(second))
|
197
|
+
|
198
|
+
if are_all_integers(first_args) and are_all_diffs(second_args):
|
199
|
+
first_args_int = cast(List[int], first_args)
|
200
|
+
second_args_diff = cast(List[Diff], second_args)
|
201
|
+
problems = [
|
202
|
+
Problem(number, diff)
|
203
|
+
for number in first_args_int
|
204
|
+
for diff in second_args_diff
|
205
|
+
]
|
206
|
+
generate_problem_directory(base_path, problems, GenerateMode.gene_path_on_num)
|
207
|
+
elif are_all_diffs(first_args) and are_all_integers(second_args):
|
208
|
+
first_args_diff = cast(List[Diff], first_args)
|
209
|
+
second_args_int = cast(List[int], second_args)
|
210
|
+
problems = [
|
211
|
+
Problem(number, diff)
|
212
|
+
for diff in first_args_diff
|
213
|
+
for number in second_args_int
|
214
|
+
]
|
215
|
+
generate_problem_directory(base_path, problems, GenerateMode.gene_path_on_diff)
|
216
|
+
else:
|
217
|
+
raise ValueError(
|
218
|
+
"""次のような形式で問題を指定してください
|
219
|
+
例 atcdr -d A 120..130 : A問題の120から130をダウンロードします
|
220
|
+
例 atcdr -d 120 : ABCのコンテストの問題をダウンロードします
|
221
|
+
"""
|
222
|
+
)
|
223
|
+
|
224
|
+
|
225
|
+
def main() -> None:
|
226
|
+
print('AtCoderの問題のHTMLファイルをダウンロードします')
|
227
|
+
print(
|
228
|
+
"""
|
229
|
+
1. 番号の範囲を指定してダウンロードする
|
230
|
+
2. 1ファイルだけダウンロードする
|
231
|
+
q: 終了
|
232
|
+
"""
|
233
|
+
)
|
234
|
+
|
235
|
+
choice = input('選択してください: ')
|
236
|
+
|
237
|
+
if choice == '1':
|
238
|
+
start_end = input(
|
239
|
+
'開始と終了のコンテストの番号をスペースで区切って指定してください (例: 223 230): '
|
240
|
+
)
|
241
|
+
start, end = map(int, start_end.split(' '))
|
242
|
+
difficulty = Diff[
|
243
|
+
input(
|
244
|
+
'ダウンロードする問題の難易度を指定してください (例: A, B, C): '
|
245
|
+
).upper()
|
246
|
+
]
|
247
|
+
problem_list = [Problem(number, difficulty) for number in range(start, end + 1)]
|
248
|
+
generate_problem_directory('.', problem_list, GenerateMode.gene_path_on_diff)
|
249
|
+
elif choice == '2':
|
250
|
+
number = int(input('コンテストの番号を指定してください: '))
|
251
|
+
difficulty = Diff[
|
252
|
+
input(
|
253
|
+
'ダウンロードする問題の難易度を指定してください (例: A, B, C): '
|
254
|
+
).upper()
|
255
|
+
]
|
256
|
+
generate_problem_directory(
|
257
|
+
'.', [Problem(number, difficulty)], GenerateMode.gene_path_on_diff
|
258
|
+
)
|
259
|
+
elif choice == 'q':
|
260
|
+
print('終了します')
|
261
|
+
else:
|
262
|
+
print('無効な選択です')
|
263
|
+
|
264
|
+
|
265
|
+
if __name__ == '__main__':
|
266
|
+
main()
|