AtCoderStudyBooster 0.1.1__py3-none-any.whl → 0.3__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 +117 -94
 - atcdr/generate.py +76 -23
 - atcdr/main.py +5 -0
 - atcdr/markdown.py +39 -0
 - atcdr/open.py +27 -20
 - atcdr/test.py +304 -142
 - atcdr/util/execute.py +63 -0
 - atcdr/util/filetype.py +105 -0
 - atcdr/util/problem.py +36 -32
 - {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/METADATA +3 -3
 - atcoderstudybooster-0.3.dist-info/RECORD +17 -0
 - atcdr/util/filename.py +0 -137
 - atcoderstudybooster-0.1.1.dist-info/RECORD +0 -15
 - {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/WHEEL +0 -0
 - {atcoderstudybooster-0.1.1.dist-info → atcoderstudybooster-0.3.dist-info}/entry_points.txt +0 -0
 
    
        atcdr/download.py
    CHANGED
    
    | 
         @@ -3,11 +3,22 @@ import re 
     | 
|
| 
       3 
3 
     | 
    
         
             
            import time
         
     | 
| 
       4 
4 
     | 
    
         
             
            from dataclasses import dataclass
         
     | 
| 
       5 
5 
     | 
    
         
             
            from enum import Enum
         
     | 
| 
       6 
     | 
    
         
            -
            from typing import Callable, List,  
     | 
| 
      
 6 
     | 
    
         
            +
            from typing import Callable, List, Optional, Union, cast
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
      
 8 
     | 
    
         
            +
            import questionary as q
         
     | 
| 
       8 
9 
     | 
    
         
             
            import requests
         
     | 
| 
      
 10 
     | 
    
         
            +
            from rich.console import Console
         
     | 
| 
      
 11 
     | 
    
         
            +
            from rich.prompt import IntPrompt, Prompt
         
     | 
| 
       9 
12 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
            from atcdr.util. 
     | 
| 
      
 13 
     | 
    
         
            +
            from atcdr.util.filetype import FILE_EXTENSIONS, Lang
         
     | 
| 
      
 14 
     | 
    
         
            +
            from atcdr.util.problem import (
         
     | 
| 
      
 15 
     | 
    
         
            +
            	get_title_from_html,
         
     | 
| 
      
 16 
     | 
    
         
            +
            	make_problem_markdown,
         
     | 
| 
      
 17 
     | 
    
         
            +
            	repair_html,
         
     | 
| 
      
 18 
     | 
    
         
            +
            	title_to_filename,
         
     | 
| 
      
 19 
     | 
    
         
            +
            )
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            console = Console()
         
     | 
| 
       11 
22 
     | 
    
         | 
| 
       12 
23 
     | 
    
         | 
| 
       13 
24 
     | 
    
         
             
            class Diff(Enum):
         
     | 
| 
         @@ -37,63 +48,42 @@ def get_problem_html(problem: Problem) -> Optional[str]: 
     | 
|
| 
       37 
48 
     | 
    
         
             
            		if response.status_code == 200:
         
     | 
| 
       38 
49 
     | 
    
         
             
            			return response.text
         
     | 
| 
       39 
50 
     | 
    
         
             
            		elif response.status_code == 429:
         
     | 
| 
       40 
     | 
    
         
            -
            			print(
         
     | 
| 
       41 
     | 
    
         
            -
            				f'[Error{response.status_code}]  
     | 
| 
      
 51 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 52 
     | 
    
         
            +
            				f'[bold yellow][Error {response.status_code}][/bold yellow] 再試行します。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
       42 
53 
     | 
    
         
             
            			)
         
     | 
| 
       43 
54 
     | 
    
         
             
            			time.sleep(retry_wait)
         
     | 
| 
       44 
55 
     | 
    
         
             
            		elif 300 <= response.status_code < 400:
         
     | 
| 
       45 
     | 
    
         
            -
            			print(
         
     | 
| 
       46 
     | 
    
         
            -
            				f'[ 
     | 
| 
      
 56 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 57 
     | 
    
         
            +
            				f'[bold yellow][Error {response.status_code}][/bold yellow] リダイレクトが発生しました。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
       47 
58 
     | 
    
         
             
            			)
         
     | 
| 
       48 
59 
     | 
    
         
             
            		elif 400 <= response.status_code < 500:
         
     | 
| 
       49 
     | 
    
         
            -
            			print(
         
     | 
| 
       50 
     | 
    
         
            -
            				f'[Error{response.status_code}] 問題が見つかりません。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
      
 60 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 61 
     | 
    
         
            +
            				f'[bold red][Error {response.status_code}][/bold red] 問題が見つかりません。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
       51 
62 
     | 
    
         
             
            			)
         
     | 
| 
       52 
63 
     | 
    
         
             
            			break
         
     | 
| 
       53 
64 
     | 
    
         
             
            		elif 500 <= response.status_code < 600:
         
     | 
| 
       54 
     | 
    
         
            -
            			print(
         
     | 
| 
       55 
     | 
    
         
            -
            				f'[Error{response.status_code}] サーバーエラーが発生しました。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
      
 65 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 66 
     | 
    
         
            +
            				f'[bold red][Error {response.status_code}][/bold red] サーバーエラーが発生しました。abc{problem.number} {problem.difficulty.value}'
         
     | 
| 
       56 
67 
     | 
    
         
             
            			)
         
     | 
| 
       57 
68 
     | 
    
         
             
            			break
         
     | 
| 
       58 
69 
     | 
    
         
             
            		else:
         
     | 
| 
       59 
     | 
    
         
            -
            			print(
         
     | 
| 
       60 
     | 
    
         
            -
            				f'[Error{response.status_code}] abc{problem.number} {problem.difficulty.value}に対応するHTMLファイルを取得できませんでした。'
         
     | 
| 
      
 70 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 71 
     | 
    
         
            +
            				f'[bold red][Error {response.status_code}][/bold red] abc{problem.number} {problem.difficulty.value}に対応するHTMLファイルを取得できませんでした。'
         
     | 
| 
       61 
72 
     | 
    
         
             
            			)
         
     | 
| 
       62 
73 
     | 
    
         
             
            			break
         
     | 
| 
       63 
74 
     | 
    
         
             
            	return None
         
     | 
| 
       64 
75 
     | 
    
         | 
| 
       65 
76 
     | 
    
         | 
| 
       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 
77 
     | 
    
         
             
            def save_file(file_path: str, html: str) -> None:
         
     | 
| 
       88 
78 
     | 
    
         
             
            	with open(file_path, 'w', encoding='utf-8') as file:
         
     | 
| 
       89 
79 
     | 
    
         
             
            		file.write(html)
         
     | 
| 
       90 
     | 
    
         
            -
            	print(f'[+] ファイルを保存しました :{file_path}')
         
     | 
| 
      
 80 
     | 
    
         
            +
            	console.print(f'[bold green][+][/bold green] ファイルを保存しました :{file_path}')
         
     | 
| 
       91 
81 
     | 
    
         | 
| 
       92 
82 
     | 
    
         | 
| 
       93 
83 
     | 
    
         
             
            def mkdir(path: str) -> None:
         
     | 
| 
       94 
84 
     | 
    
         
             
            	if not os.path.exists(path):
         
     | 
| 
       95 
85 
     | 
    
         
             
            		os.makedirs(path)
         
     | 
| 
       96 
     | 
    
         
            -
            		print(f'[+] フォルダー: {path} を作成しました')
         
     | 
| 
      
 86 
     | 
    
         
            +
            		console.print(f'[bold green][+][/bold green] フォルダー: {path} を作成しました')
         
     | 
| 
       97 
87 
     | 
    
         | 
| 
       98 
88 
     | 
    
         | 
| 
       99 
89 
     | 
    
         
             
            class GenerateMode:
         
     | 
| 
         @@ -117,17 +107,20 @@ def generate_problem_directory( 
     | 
|
| 
       117 
107 
     | 
    
         
             
            			continue
         
     | 
| 
       118 
108 
     | 
    
         | 
| 
       119 
109 
     | 
    
         
             
            		title = get_title_from_html(html)
         
     | 
| 
       120 
     | 
    
         
            -
            		if title 
     | 
| 
       121 
     | 
    
         
            -
            			print('[Error] タイトルが取得できませんでした')
         
     | 
| 
      
 110 
     | 
    
         
            +
            		if not title:
         
     | 
| 
      
 111 
     | 
    
         
            +
            			console.print('[bold red][Error][/bold red] タイトルが取得できませんでした')
         
     | 
| 
       122 
112 
     | 
    
         
             
            			title = f'problem{problem.number}{problem.difficulty.value}'
         
     | 
| 
       123 
113 
     | 
    
         | 
| 
      
 114 
     | 
    
         
            +
            		title = title_to_filename(title)
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
       124 
116 
     | 
    
         
             
            		mkdir(dir_path)
         
     | 
| 
       125 
117 
     | 
    
         
             
            		repaired_html = repair_html(html)
         
     | 
| 
       126 
118 
     | 
    
         | 
| 
       127 
     | 
    
         
            -
            		html_path = os.path.join(dir_path,  
     | 
| 
      
 119 
     | 
    
         
            +
            		html_path = os.path.join(dir_path, title + FILE_EXTENSIONS[Lang.HTML])
         
     | 
| 
       128 
120 
     | 
    
         
             
            		save_file(html_path, repaired_html)
         
     | 
| 
       129 
121 
     | 
    
         
             
            		md = make_problem_markdown(html, 'ja')
         
     | 
| 
       130 
     | 
    
         
            -
            		 
     | 
| 
      
 122 
     | 
    
         
            +
            		md_path = os.path.join(dir_path, title + FILE_EXTENSIONS[Lang.MARKDOWN])
         
     | 
| 
      
 123 
     | 
    
         
            +
            		save_file(md_path, md)
         
     | 
| 
       131 
124 
     | 
    
         | 
| 
       132 
125 
     | 
    
         | 
| 
       133 
126 
     | 
    
         
             
            def parse_range(range_str: str) -> List[int]:
         
     | 
| 
         @@ -136,11 +129,11 @@ def parse_range(range_str: str) -> List[int]: 
     | 
|
| 
       136 
129 
     | 
    
         
             
            		start, end = map(int, match.groups())
         
     | 
| 
       137 
130 
     | 
    
         
             
            		return list(range(start, end + 1))
         
     | 
| 
       138 
131 
     | 
    
         
             
            	else:
         
     | 
| 
       139 
     | 
    
         
            -
            		raise ValueError(' 
     | 
| 
      
 132 
     | 
    
         
            +
            		raise ValueError('数字の範囲の形式が間違っています')
         
     | 
| 
       140 
133 
     | 
    
         | 
| 
       141 
134 
     | 
    
         | 
| 
       142 
135 
     | 
    
         
             
            def parse_diff_range(range_str: str) -> List[Diff]:
         
     | 
| 
       143 
     | 
    
         
            -
            	match = re.match(r'^([A- 
     | 
| 
      
 136 
     | 
    
         
            +
            	match = re.match(r'^([A-Z])\.\.([A-Z])$', range_str)
         
     | 
| 
       144 
137 
     | 
    
         
             
            	if match:
         
     | 
| 
       145 
138 
     | 
    
         
             
            		start, end = match.groups()
         
     | 
| 
       146 
139 
     | 
    
         
             
            		start_index = ord(start) - ord('A')
         
     | 
| 
         @@ -150,7 +143,7 @@ def parse_diff_range(range_str: str) -> List[Diff]: 
     | 
|
| 
       150 
143 
     | 
    
         
             
            	raise ValueError('A..C の形式になっていません')
         
     | 
| 
       151 
144 
     | 
    
         | 
| 
       152 
145 
     | 
    
         | 
| 
       153 
     | 
    
         
            -
            def convert_arg(arg:  
     | 
| 
      
 146 
     | 
    
         
            +
            def convert_arg(arg: str) -> Union[List[int], List[Diff]]:
         
     | 
| 
       154 
147 
     | 
    
         
             
            	if isinstance(arg, int):
         
     | 
| 
       155 
148 
     | 
    
         
             
            		return [arg]
         
     | 
| 
       156 
149 
     | 
    
         
             
            	elif isinstance(arg, str):
         
     | 
| 
         @@ -160,7 +153,7 @@ def convert_arg(arg: Union[str, int]) -> Union[List[int], List[Diff]]: 
     | 
|
| 
       160 
153 
     | 
    
         
             
            			return [Diff[arg]]
         
     | 
| 
       161 
154 
     | 
    
         
             
            		elif re.match(r'^\d+\.\.\d+$', arg):
         
     | 
| 
       162 
155 
     | 
    
         
             
            			return parse_range(arg)
         
     | 
| 
       163 
     | 
    
         
            -
            		elif re.match(r'^[A- 
     | 
| 
      
 156 
     | 
    
         
            +
            		elif re.match(r'^[A-Z]\.\.[A-Z]$', arg):
         
     | 
| 
       164 
157 
     | 
    
         
             
            			return parse_diff_range(arg)
         
     | 
| 
       165 
158 
     | 
    
         
             
            	raise ValueError(f'{arg}は認識できません')
         
     | 
| 
       166 
159 
     | 
    
         | 
| 
         @@ -173,13 +166,87 @@ def are_all_diffs(args: Union[List[int], List[Diff]]) -> bool: 
     | 
|
| 
       173 
166 
     | 
    
         
             
            	return all(isinstance(arg, Diff) for arg in args)
         
     | 
| 
       174 
167 
     | 
    
         | 
| 
       175 
168 
     | 
    
         | 
| 
      
 169 
     | 
    
         
            +
            def interactive_download() -> None:
         
     | 
| 
      
 170 
     | 
    
         
            +
            	CONTEST = '1. 特定のコンテストの問題を解きたい'
         
     | 
| 
      
 171 
     | 
    
         
            +
            	PRACTICE = '2. 特定の難易度の問題を集中的に練習したい'
         
     | 
| 
      
 172 
     | 
    
         
            +
            	ONE_FILE = '3. 1ファイルだけダウンロードする'
         
     | 
| 
      
 173 
     | 
    
         
            +
            	END = '4. 終了する'
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
            	choice = q.select(
         
     | 
| 
      
 176 
     | 
    
         
            +
            		message='AtCoderの問題のHTMLファイルをダウンロードします',
         
     | 
| 
      
 177 
     | 
    
         
            +
            		qmark='',
         
     | 
| 
      
 178 
     | 
    
         
            +
            		pointer='❯❯❯',
         
     | 
| 
      
 179 
     | 
    
         
            +
            		choices=[CONTEST, PRACTICE, ONE_FILE, END],
         
     | 
| 
      
 180 
     | 
    
         
            +
            		instruction='\n 十字キーで移動,[enter]で実行',
         
     | 
| 
      
 181 
     | 
    
         
            +
            		style=q.Style(
         
     | 
| 
      
 182 
     | 
    
         
            +
            			[
         
     | 
| 
      
 183 
     | 
    
         
            +
            				('question', 'fg:#2196F3 bold'),
         
     | 
| 
      
 184 
     | 
    
         
            +
            				('answer', 'fg:#FFB300 bold'),
         
     | 
| 
      
 185 
     | 
    
         
            +
            				('pointer', 'fg:#FFB300 bold'),
         
     | 
| 
      
 186 
     | 
    
         
            +
            				('highlighted', 'fg:#FFB300 bold'),
         
     | 
| 
      
 187 
     | 
    
         
            +
            				('selected', 'fg:#FFB300 bold'),
         
     | 
| 
      
 188 
     | 
    
         
            +
            			]
         
     | 
| 
      
 189 
     | 
    
         
            +
            		),
         
     | 
| 
      
 190 
     | 
    
         
            +
            	).ask()
         
     | 
| 
      
 191 
     | 
    
         
            +
             
     | 
| 
      
 192 
     | 
    
         
            +
            	if choice == CONTEST:
         
     | 
| 
      
 193 
     | 
    
         
            +
            		number = IntPrompt.ask(
         
     | 
| 
      
 194 
     | 
    
         
            +
            			'コンテスト番号を入力してください (例: 120)',
         
     | 
| 
      
 195 
     | 
    
         
            +
            		)
         
     | 
| 
      
 196 
     | 
    
         
            +
            		contest_diffs = list(Diff)
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
            		problems = [Problem(number, diff) for diff in contest_diffs]
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
            		generate_problem_directory('.', problems, GenerateMode.gene_path_on_num)
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
            	elif choice == PRACTICE:
         
     | 
| 
      
 203 
     | 
    
         
            +
            		diff = Prompt.ask(
         
     | 
| 
      
 204 
     | 
    
         
            +
            			'難易度を入力してください (例: A)',
         
     | 
| 
      
 205 
     | 
    
         
            +
            		)
         
     | 
| 
      
 206 
     | 
    
         
            +
            		try:
         
     | 
| 
      
 207 
     | 
    
         
            +
            			diff = Diff[diff.upper()]
         
     | 
| 
      
 208 
     | 
    
         
            +
            		except KeyError:
         
     | 
| 
      
 209 
     | 
    
         
            +
            			raise ValueError('入力された難易度が認識できません')
         
     | 
| 
      
 210 
     | 
    
         
            +
            		number_str = Prompt.ask(
         
     | 
| 
      
 211 
     | 
    
         
            +
            			'コンテスト番号または範囲を入力してください (例: 120..130)'
         
     | 
| 
      
 212 
     | 
    
         
            +
            		)
         
     | 
| 
      
 213 
     | 
    
         
            +
            		if number_str.isdigit():
         
     | 
| 
      
 214 
     | 
    
         
            +
            			contest_numbers = [int(number_str)]
         
     | 
| 
      
 215 
     | 
    
         
            +
            		elif re.match(r'^\d+\.\.\d+$', number_str):
         
     | 
| 
      
 216 
     | 
    
         
            +
            			contest_numbers = parse_range(number_str)
         
     | 
| 
      
 217 
     | 
    
         
            +
            		else:
         
     | 
| 
      
 218 
     | 
    
         
            +
            			raise ValueError('数字の範囲の形式が間違っています')
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
            		problems = [Problem(number, diff) for number in contest_numbers]
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
            		generate_problem_directory('.', problems, GenerateMode.gene_path_on_diff)
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
            	elif choice == ONE_FILE:
         
     | 
| 
      
 225 
     | 
    
         
            +
            		contest_number = IntPrompt.ask(
         
     | 
| 
      
 226 
     | 
    
         
            +
            			'コンテスト番号を入力してください (例: 120)',
         
     | 
| 
      
 227 
     | 
    
         
            +
            		)
         
     | 
| 
      
 228 
     | 
    
         
            +
            		difficulty = Prompt.ask(
         
     | 
| 
      
 229 
     | 
    
         
            +
            			'難易度を入力してください (例: A)', choices=[d.name for d in Diff]
         
     | 
| 
      
 230 
     | 
    
         
            +
            		)
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
            		difficulty = difficulty.upper().strip()
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            		problem = Problem(contest_number, Diff[difficulty])
         
     | 
| 
      
 235 
     | 
    
         
            +
            		generate_problem_directory('.', [problem], GenerateMode.gene_path_on_num)
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
            	elif choice == END:
         
     | 
| 
      
 238 
     | 
    
         
            +
            		console.print('終了します', style='bold red')
         
     | 
| 
      
 239 
     | 
    
         
            +
            	else:
         
     | 
| 
      
 240 
     | 
    
         
            +
            		console.print('無効な選択です', style='bold red')
         
     | 
| 
      
 241 
     | 
    
         
            +
             
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
       176 
243 
     | 
    
         
             
            def download(
         
     | 
| 
       177 
244 
     | 
    
         
             
            	first: Union[str, int, None] = None,
         
     | 
| 
       178 
245 
     | 
    
         
             
            	second: Union[str, int, None] = None,
         
     | 
| 
       179 
246 
     | 
    
         
             
            	base_path: str = '.',
         
     | 
| 
       180 
247 
     | 
    
         
             
            ) -> None:
         
     | 
| 
       181 
248 
     | 
    
         
             
            	if first is None:
         
     | 
| 
       182 
     | 
    
         
            -
            		 
     | 
| 
      
 249 
     | 
    
         
            +
            		interactive_download()
         
     | 
| 
       183 
250 
     | 
    
         
             
            		return
         
     | 
| 
       184 
251 
     | 
    
         | 
| 
       185 
252 
     | 
    
         
             
            	first_args = convert_arg(str(first))
         
     | 
| 
         @@ -187,9 +254,9 @@ def download( 
     | 
|
| 
       187 
254 
     | 
    
         
             
            		if isinstance(first, Diff):
         
     | 
| 
       188 
255 
     | 
    
         
             
            			raise ValueError(
         
     | 
| 
       189 
256 
     | 
    
         
             
            				"""難易度だけでなく, 問題番号も指定してコマンドを実行してください.
         
     | 
| 
       190 
     | 
    
         
            -
             
     | 
| 
       191 
     | 
    
         
            -
             
     | 
| 
       192 
     | 
    
         
            -
             
     | 
| 
      
 257 
     | 
    
         
            +
                                例 atcdr -d A 120  : A問題の120をダウンロードます
         
     | 
| 
      
 258 
     | 
    
         
            +
                                例 atcdr -d A 120..130  : A問題の120から130をダウンロードます
         
     | 
| 
      
 259 
     | 
    
         
            +
                            """
         
     | 
| 
       193 
260 
     | 
    
         
             
            			)
         
     | 
| 
       194 
261 
     | 
    
         
             
            		second_args: Union[List[int], List[Diff]] = list(Diff)
         
     | 
| 
       195 
262 
     | 
    
         
             
            	else:
         
     | 
| 
         @@ -216,51 +283,7 @@ def download( 
     | 
|
| 
       216 
283 
     | 
    
         
             
            	else:
         
     | 
| 
       217 
284 
     | 
    
         
             
            		raise ValueError(
         
     | 
| 
       218 
285 
     | 
    
         
             
            			"""次のような形式で問題を指定してください
         
     | 
| 
       219 
     | 
    
         
            -
             
     | 
| 
       220 
     | 
    
         
            -
             
     | 
| 
       221 
     | 
    
         
            -
             
     | 
| 
      
 286 
     | 
    
         
            +
                            例 atcdr -d A 120..130  : A問題の120から130をダウンロードします
         
     | 
| 
      
 287 
     | 
    
         
            +
                            例 atcdr -d 120         : ABCのコンテストの問題をダウンロードします
         
     | 
| 
      
 288 
     | 
    
         
            +
                        """
         
     | 
| 
       222 
289 
     | 
    
         
             
            		)
         
     | 
| 
       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()
         
     | 
    
        atcdr/generate.py
    CHANGED
    
    | 
         @@ -2,17 +2,20 @@ import json 
     | 
|
| 
       2 
2 
     | 
    
         
             
            import os
         
     | 
| 
       3 
3 
     | 
    
         
             
            import re
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            from rich.console import Console
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
       5 
7 
     | 
    
         
             
            from atcdr.test import (
         
     | 
| 
      
 8 
     | 
    
         
            +
            	LabeledTestCaseResult,
         
     | 
| 
       6 
9 
     | 
    
         
             
            	ResultStatus,
         
     | 
| 
       7 
10 
     | 
    
         
             
            	create_testcases_from_html,
         
     | 
| 
       8 
11 
     | 
    
         
             
            	judge_code_from,
         
     | 
| 
       9 
     | 
    
         
            -
            	 
     | 
| 
      
 12 
     | 
    
         
            +
            	render_results,
         
     | 
| 
       10 
13 
     | 
    
         
             
            )
         
     | 
| 
       11 
     | 
    
         
            -
            from atcdr.util. 
     | 
| 
      
 14 
     | 
    
         
            +
            from atcdr.util.execute import execute_files
         
     | 
| 
      
 15 
     | 
    
         
            +
            from atcdr.util.filetype import (
         
     | 
| 
       12 
16 
     | 
    
         
             
            	FILE_EXTENSIONS,
         
     | 
| 
       13 
17 
     | 
    
         
             
            	Filename,
         
     | 
| 
       14 
18 
     | 
    
         
             
            	Lang,
         
     | 
| 
       15 
     | 
    
         
            -
            	execute_files,
         
     | 
| 
       16 
19 
     | 
    
         
             
            	lang2str,
         
     | 
| 
       17 
20 
     | 
    
         
             
            	str2lang,
         
     | 
| 
       18 
21 
     | 
    
         
             
            )
         
     | 
| 
         @@ -26,7 +29,38 @@ def get_code_from_gpt_output(output: str) -> str: 
     | 
|
| 
       26 
29 
     | 
    
         
             
            	return match.group(1) if match else ''
         
     | 
| 
       27 
30 
     | 
    
         | 
| 
       28 
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 
     | 
    
         
            +
             
     | 
| 
       29 
62 
     | 
    
         
             
            def generate_code(file: Filename, lang: Lang) -> None:
         
     | 
| 
      
 63 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
       30 
64 
     | 
    
         
             
            	with open(file, 'r') as f:
         
     | 
| 
       31 
65 
     | 
    
         
             
            		html_content = f.read()
         
     | 
| 
       32 
66 
     | 
    
         
             
            	md = make_problem_markdown(html_content, 'en')
         
     | 
| 
         @@ -36,20 +70,25 @@ def generate_code(file: Filename, lang: Lang) -> None: 
     | 
|
| 
       36 
70 
     | 
    
         
             
            	gpt = ChatGPT(
         
     | 
| 
       37 
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)}.""",
         
     | 
| 
       38 
72 
     | 
    
         
             
            	)
         
     | 
| 
      
 73 
     | 
    
         
            +
            	with console.status(f'{gpt.model.value}がコードを生成しています...'):
         
     | 
| 
      
 74 
     | 
    
         
            +
            		reply = gpt.tell(md)
         
     | 
| 
       39 
75 
     | 
    
         | 
| 
       40 
     | 
    
         
            -
            	reply = gpt.tell(md)
         
     | 
| 
       41 
76 
     | 
    
         
             
            	code = get_code_from_gpt_output(reply)
         
     | 
| 
       42 
     | 
    
         
            -
            	print(f'AI利用にかかったAPIコスト: {gpt.sum_cost}')
         
     | 
| 
       43 
77 
     | 
    
         | 
| 
       44 
78 
     | 
    
         
             
            	saved_filename = (
         
     | 
| 
       45 
79 
     | 
    
         
             
            		os.path.splitext(file)[0] + f'_by_{gpt.model.value}' + FILE_EXTENSIONS[lang]
         
     | 
| 
       46 
80 
     | 
    
         
             
            	)
         
     | 
| 
       47 
81 
     | 
    
         
             
            	with open(saved_filename, 'w') as f:
         
     | 
| 
       48 
     | 
    
         
            -
            		print( 
     | 
| 
      
 82 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 83 
     | 
    
         
            +
            			f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
         
     | 
| 
      
 84 
     | 
    
         
            +
            		)
         
     | 
| 
       49 
85 
     | 
    
         
             
            		f.write(code)
         
     | 
| 
       50 
86 
     | 
    
         | 
| 
      
 87 
     | 
    
         
            +
            	console.print(f'[info] AI利用にかかったAPIコスト: {gpt.sum_cost}')
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
       51 
89 
     | 
    
         | 
| 
       52 
90 
     | 
    
         
             
            def generate_template(file: Filename, lang: Lang) -> None:
         
     | 
| 
      
 91 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
       53 
92 
     | 
    
         
             
            	with open(file, 'r') as f:
         
     | 
| 
       54 
93 
     | 
    
         
             
            		html_content = f.read()
         
     | 
| 
       55 
94 
     | 
    
         
             
            	md = make_problem_markdown(html_content, 'en')
         
     | 
| 
         @@ -70,17 +109,22 @@ The user will provide a problem from a programming contest called AtCoder. This 
     | 
|
| 
       70 
109 
     | 
    
         | 
| 
       71 
110 
     | 
    
         
             
            You must not solve the problem. Please faithfully reproduce the variable names defined in the problem.
         
     | 
| 
       72 
111 
     | 
    
         
             
                """
         
     | 
| 
       73 
     | 
    
         
            -
            	 
     | 
| 
      
 112 
     | 
    
         
            +
            	with console.status(f'{lang2str(lang)}のテンプレートを生成しています...'):
         
     | 
| 
      
 113 
     | 
    
         
            +
            		reply = gpt.tell(md + propmpt)
         
     | 
| 
       74 
114 
     | 
    
         
             
            	code = get_code_from_gpt_output(reply)
         
     | 
| 
       75 
     | 
    
         
            -
            	print(f'AI利用にかかったAPIコスト:{gpt.sum_cost}')
         
     | 
| 
       76 
115 
     | 
    
         | 
| 
       77 
116 
     | 
    
         
             
            	savaed_filename = os.path.splitext(file)[0] + FILE_EXTENSIONS[lang]
         
     | 
| 
       78 
     | 
    
         
            -
            	with open(savaed_filename, ' 
     | 
| 
       79 
     | 
    
         
            -
            		print( 
     | 
| 
      
 117 
     | 
    
         
            +
            	with open(savaed_filename, 'x') as f:
         
     | 
| 
      
 118 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 119 
     | 
    
         
            +
            			f'[green][+][/green] テンプレートファイルを作成 :{savaed_filename}'
         
     | 
| 
      
 120 
     | 
    
         
            +
            		)
         
     | 
| 
       80 
121 
     | 
    
         
             
            		f.write(code)
         
     | 
| 
       81 
122 
     | 
    
         | 
| 
      
 123 
     | 
    
         
            +
            	console.print(f'[info] AI利用にかかったAPIコスト: {gpt.sum_cost}')
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
       82 
125 
     | 
    
         | 
| 
       83 
126 
     | 
    
         
             
            def solve_problem(file: Filename, lang: Lang) -> None:
         
     | 
| 
      
 127 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
       84 
128 
     | 
    
         
             
            	with open(file, 'r') as f:
         
     | 
| 
       85 
129 
     | 
    
         
             
            		html_content = f.read()
         
     | 
| 
       86 
130 
     | 
    
         
             
            	md = make_problem_markdown(html_content, 'en')
         
     | 
| 
         @@ -94,9 +138,16 @@ def solve_problem(file: Filename, lang: Lang) -> None: 
     | 
|
| 
       94 
138 
     | 
    
         | 
| 
       95 
139 
     | 
    
         
             
            	file_without_ext = os.path.splitext(file)[0]
         
     | 
| 
       96 
140 
     | 
    
         | 
| 
       97 
     | 
    
         
            -
            	reply = gpt.tell(md)
         
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
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 
     | 
    
         
            +
             
     | 
| 
       100 
151 
     | 
    
         
             
            		code = get_code_from_gpt_output(reply)
         
     | 
| 
       101 
152 
     | 
    
         | 
| 
       102 
153 
     | 
    
         
             
            		saved_filename = (
         
     | 
| 
         @@ -106,25 +157,25 @@ def solve_problem(file: Filename, lang: Lang) -> None: 
     | 
|
| 
       106 
157 
     | 
    
         
             
            			+ FILE_EXTENSIONS[lang]
         
     | 
| 
       107 
158 
     | 
    
         
             
            		)
         
     | 
| 
       108 
159 
     | 
    
         
             
            		with open(saved_filename, 'w') as f:
         
     | 
| 
       109 
     | 
    
         
            -
            			print( 
     | 
| 
      
 160 
     | 
    
         
            +
            			console.print(
         
     | 
| 
      
 161 
     | 
    
         
            +
            				f'[green][+][/green] {gpt.model.value} の出力したコードを保存しました:{f.name}'
         
     | 
| 
      
 162 
     | 
    
         
            +
            			)
         
     | 
| 
       110 
163 
     | 
    
         
             
            			f.write(code)
         
     | 
| 
       111 
164 
     | 
    
         | 
| 
       112 
165 
     | 
    
         
             
            		labeled_results = judge_code_from(labeled_cases, saved_filename)
         
     | 
| 
       113 
     | 
    
         
            -
            		test_report = '\n'.join( 
     | 
| 
      
 166 
     | 
    
         
            +
            		test_report = '\n'.join(
         
     | 
| 
      
 167 
     | 
    
         
            +
            			render_result_for_GPT(lresult) for lresult in labeled_results
         
     | 
| 
      
 168 
     | 
    
         
            +
            		)
         
     | 
| 
       114 
169 
     | 
    
         | 
| 
       115 
     | 
    
         
            -
            		 
     | 
| 
       116 
     | 
    
         
            -
            		 
     | 
| 
      
 170 
     | 
    
         
            +
            		console.rule(f'{i}回目のコード生成でのテスト結果')
         
     | 
| 
      
 171 
     | 
    
         
            +
            		render_results(saved_filename, labeled_results)
         
     | 
| 
       117 
172 
     | 
    
         | 
| 
       118 
173 
     | 
    
         
             
            		if all(
         
     | 
| 
       119 
174 
     | 
    
         
             
            			labeled_result.result.passed == ResultStatus.AC
         
     | 
| 
       120 
175 
     | 
    
         
             
            			for labeled_result in labeled_results
         
     | 
| 
       121 
176 
     | 
    
         
             
            		):
         
     | 
| 
       122 
     | 
    
         
            -
            			print('コードのテストに成功!')
         
     | 
| 
      
 177 
     | 
    
         
            +
            			console.print('[green]コードのテストに成功![/green]')
         
     | 
| 
       123 
178 
     | 
    
         
             
            			break
         
     | 
| 
       124 
     | 
    
         
            -
            		else:
         
     | 
| 
       125 
     | 
    
         
            -
            			reply = gpt.tell(f"""The following is the test report for the code you provided:
         
     | 
| 
       126 
     | 
    
         
            -
            {test_report}
         
     | 
| 
       127 
     | 
    
         
            -
            Please provide an updated version of the code in {lang2str(lang)}.""")
         
     | 
| 
       128 
179 
     | 
    
         | 
| 
       129 
180 
     | 
    
         
             
            	with open(
         
     | 
| 
       130 
181 
     | 
    
         
             
            		'log_'
         
     | 
| 
         @@ -133,9 +184,11 @@ Please provide an updated version of the code in {lang2str(lang)}.""") 
     | 
|
| 
       133 
184 
     | 
    
         
             
            		+ FILE_EXTENSIONS[Lang.JSON],
         
     | 
| 
       134 
185 
     | 
    
         
             
            		'w',
         
     | 
| 
       135 
186 
     | 
    
         
             
            	) as f:
         
     | 
| 
       136 
     | 
    
         
            -
            		print( 
     | 
| 
      
 187 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 188 
     | 
    
         
            +
            			f'[green][+][/green] {gpt.model.value}の出力のログを保存しました:{f.name}'
         
     | 
| 
      
 189 
     | 
    
         
            +
            		)
         
     | 
| 
       137 
190 
     | 
    
         
             
            		f.write(json.dumps(gpt.messages, indent=2))
         
     | 
| 
       138 
     | 
    
         
            -
            	print(f'AI利用にかかったAPIコスト:{gpt.sum_cost}')
         
     | 
| 
      
 191 
     | 
    
         
            +
            	console.print(f'AI利用にかかったAPIコスト:{gpt.sum_cost}')
         
     | 
| 
       139 
192 
     | 
    
         
             
            	return
         
     | 
| 
       140 
193 
     | 
    
         | 
| 
       141 
194 
     | 
    
         | 
    
        atcdr/main.py
    CHANGED
    
    | 
         @@ -1,9 +1,11 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            from importlib.metadata import metadata
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            import fire  # type: ignore
         
     | 
| 
      
 4 
     | 
    
         
            +
            from rich.traceback import install
         
     | 
| 
       4 
5 
     | 
    
         | 
| 
       5 
6 
     | 
    
         
             
            from atcdr.download import download
         
     | 
| 
       6 
7 
     | 
    
         
             
            from atcdr.generate import generate
         
     | 
| 
      
 8 
     | 
    
         
            +
            from atcdr.markdown import markdown
         
     | 
| 
       7 
9 
     | 
    
         
             
            from atcdr.open import open_files
         
     | 
| 
       8 
10 
     | 
    
         
             
            from atcdr.test import test
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
         @@ -22,12 +24,15 @@ MAP_COMMANDS: dict = { 
     | 
|
| 
       22 
24 
     | 
    
         
             
            	'o': open_files,
         
     | 
| 
       23 
25 
     | 
    
         
             
            	'generate': generate,
         
     | 
| 
       24 
26 
     | 
    
         
             
            	'g': generate,
         
     | 
| 
      
 27 
     | 
    
         
            +
            	'markdown': markdown,
         
     | 
| 
      
 28 
     | 
    
         
            +
            	'md': markdown,
         
     | 
| 
       25 
29 
     | 
    
         
             
            	'--version': get_version,
         
     | 
| 
       26 
30 
     | 
    
         
             
            	'-v': get_version,
         
     | 
| 
       27 
31 
     | 
    
         
             
            }
         
     | 
| 
       28 
32 
     | 
    
         | 
| 
       29 
33 
     | 
    
         | 
| 
       30 
34 
     | 
    
         
             
            def main():
         
     | 
| 
      
 35 
     | 
    
         
            +
            	install()
         
     | 
| 
       31 
36 
     | 
    
         
             
            	fire.Fire(MAP_COMMANDS)
         
     | 
| 
       32 
37 
     | 
    
         | 
| 
       33 
38 
     | 
    
         | 
    
        atcdr/markdown.py
    ADDED
    
    | 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            import os
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            from rich.console import Console
         
     | 
| 
      
 4 
     | 
    
         
            +
            from rich.markdown import Markdown
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            from atcdr.util.execute import execute_files
         
     | 
| 
      
 7 
     | 
    
         
            +
            from atcdr.util.filetype import FILE_EXTENSIONS, Lang
         
     | 
| 
      
 8 
     | 
    
         
            +
            from atcdr.util.problem import make_problem_markdown
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            def save_markdown(html_path: str, lang: str) -> None:
         
     | 
| 
      
 12 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
      
 13 
     | 
    
         
            +
            	with open(html_path, 'r', encoding='utf-8') as f:
         
     | 
| 
      
 14 
     | 
    
         
            +
            		html = f.read()
         
     | 
| 
      
 15 
     | 
    
         
            +
            	md = make_problem_markdown(html, lang)
         
     | 
| 
      
 16 
     | 
    
         
            +
            	file_without_ext = os.path.splitext(html_path)[0]
         
     | 
| 
      
 17 
     | 
    
         
            +
            	md_path = file_without_ext + FILE_EXTENSIONS[Lang.MARKDOWN]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            	with open(md_path, 'w', encoding='utf-8') as f:
         
     | 
| 
      
 20 
     | 
    
         
            +
            		f.write(md)
         
     | 
| 
      
 21 
     | 
    
         
            +
            		console.print('[green][+][/green] Markdownファイルを作成しました.')
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            def print_markdown(md_path: str) -> None:
         
     | 
| 
      
 25 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
      
 26 
     | 
    
         
            +
            	with open(md_path, 'r', encoding='utf-8') as f:
         
     | 
| 
      
 27 
     | 
    
         
            +
            		md = f.read()
         
     | 
| 
      
 28 
     | 
    
         
            +
            	console.print(Markdown(md))
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            def markdown(*args: str, lang: str = 'ja', save: bool = False) -> None:
         
     | 
| 
      
 32 
     | 
    
         
            +
            	if save:
         
     | 
| 
      
 33 
     | 
    
         
            +
            		execute_files(
         
     | 
| 
      
 34 
     | 
    
         
            +
            			*args,
         
     | 
| 
      
 35 
     | 
    
         
            +
            			func=lambda html_path: save_markdown(html_path, lang),
         
     | 
| 
      
 36 
     | 
    
         
            +
            			target_filetypes=[Lang.HTML],
         
     | 
| 
      
 37 
     | 
    
         
            +
            		)
         
     | 
| 
      
 38 
     | 
    
         
            +
            	else:
         
     | 
| 
      
 39 
     | 
    
         
            +
            		execute_files(*args, func=print_markdown, target_filetypes=[Lang.MARKDOWN])
         
     | 
    
        atcdr/open.py
    CHANGED
    
    | 
         @@ -1,35 +1,42 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            import webbrowser
         
     | 
| 
      
 1 
     | 
    
         
            +
            import webbrowser  # noqa: I001
         
     | 
| 
      
 2 
     | 
    
         
            +
            from rich.panel import Panel
         
     | 
| 
      
 3 
     | 
    
         
            +
            from rich.console import Console
         
     | 
| 
       2 
4 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            from  
     | 
| 
       4 
     | 
    
         
            -
            from  
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
            from atcdr.util.filename import Lang, execute_files
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
            def find_link_from(html: str) -> str | None:
         
     | 
| 
       10 
     | 
    
         
            -
            	soup = bs(html, 'html.parser')
         
     | 
| 
       11 
     | 
    
         
            -
            	meta_tag = soup.find('meta', property='og:url')
         
     | 
| 
       12 
     | 
    
         
            -
            	if isinstance(meta_tag, Tag) and 'content' in meta_tag.attrs:
         
     | 
| 
       13 
     | 
    
         
            -
            		content = meta_tag['content']
         
     | 
| 
       14 
     | 
    
         
            -
            		if isinstance(content, list):
         
     | 
| 
       15 
     | 
    
         
            -
            			return content[0]  # 必要に応じて、最初の要素を返す
         
     | 
| 
       16 
     | 
    
         
            -
            		return content
         
     | 
| 
       17 
     | 
    
         
            -
            	return None
         
     | 
| 
      
 5 
     | 
    
         
            +
            from atcdr.util.filetype import Lang
         
     | 
| 
      
 6 
     | 
    
         
            +
            from atcdr.util.execute import execute_files
         
     | 
| 
      
 7 
     | 
    
         
            +
            from atcdr.util.problem import find_link_from_html
         
     | 
| 
       18 
8 
     | 
    
         | 
| 
       19 
9 
     | 
    
         | 
| 
       20 
10 
     | 
    
         
             
            def open_html(file: str) -> None:
         
     | 
| 
      
 11 
     | 
    
         
            +
            	console = Console()
         
     | 
| 
       21 
12 
     | 
    
         
             
            	try:
         
     | 
| 
       22 
13 
     | 
    
         
             
            		with open(file, 'r') as f:
         
     | 
| 
       23 
14 
     | 
    
         
             
            			html_content = f.read()
         
     | 
| 
       24 
15 
     | 
    
         
             
            	except FileNotFoundError:
         
     | 
| 
       25 
     | 
    
         
            -
            		print( 
     | 
| 
      
 16 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 17 
     | 
    
         
            +
            			Panel(
         
     | 
| 
      
 18 
     | 
    
         
            +
            				f"{file}' [red]が見つかりません[/]",
         
     | 
| 
      
 19 
     | 
    
         
            +
            				border_style='red',
         
     | 
| 
      
 20 
     | 
    
         
            +
            			)
         
     | 
| 
      
 21 
     | 
    
         
            +
            		)
         
     | 
| 
       26 
22 
     | 
    
         
             
            		return
         
     | 
| 
       27 
23 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
            	url =  
     | 
| 
      
 24 
     | 
    
         
            +
            	url = find_link_from_html(html_content)
         
     | 
| 
       29 
25 
     | 
    
         
             
            	if url:
         
     | 
| 
       30 
     | 
    
         
            -
            		webbrowser. 
     | 
| 
      
 26 
     | 
    
         
            +
            		webbrowser.open_new_tab(url)
         
     | 
| 
      
 27 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 28 
     | 
    
         
            +
            			Panel(
         
     | 
| 
      
 29 
     | 
    
         
            +
            				f'[green]URLを開きました[/] {url}',
         
     | 
| 
      
 30 
     | 
    
         
            +
            				border_style='green',
         
     | 
| 
      
 31 
     | 
    
         
            +
            			)
         
     | 
| 
      
 32 
     | 
    
         
            +
            		)
         
     | 
| 
       31 
33 
     | 
    
         
             
            	else:
         
     | 
| 
       32 
     | 
    
         
            -
            		print( 
     | 
| 
      
 34 
     | 
    
         
            +
            		console.print(
         
     | 
| 
      
 35 
     | 
    
         
            +
            			Panel(
         
     | 
| 
      
 36 
     | 
    
         
            +
            				f'{file} [yellow]にURLが見つかりませんでした[/]',
         
     | 
| 
      
 37 
     | 
    
         
            +
            				border_style='yellow',
         
     | 
| 
      
 38 
     | 
    
         
            +
            			)
         
     | 
| 
      
 39 
     | 
    
         
            +
            		)
         
     | 
| 
       33 
40 
     | 
    
         | 
| 
       34 
41 
     | 
    
         | 
| 
       35 
42 
     | 
    
         
             
            def open_files(*args: str) -> None:
         
     |