-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnew_problem.py
More file actions
executable file
Β·269 lines (220 loc) Β· 8.85 KB
/
new_problem.py
File metadata and controls
executable file
Β·269 lines (220 loc) Β· 8.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env python3
"""
Script to create a new Advent of Code problem directory structure.
Usage:
python new_problem.py <year> <day> [problem_name]
Examples:
python new_problem.py 2024 3 # Automatically fetch problem name
python new_problem.py 2024 3 TobogganTrajectory # Manually specify problem name
"""
import os
import re
import ssl
import sys
from pathlib import Path
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError
def load_template():
"""
Load the solution template from the template file.
Returns:
The template content as a string, or None if the template file is not found.
"""
# Get the script's directory (repository root)
repo_root = Path(__file__).parent
template_file = repo_root / ".template" / "solution.py"
try:
return template_file.read_text()
except FileNotFoundError:
print(f"β Error: Template file not found at {template_file}")
print("Please ensure .template/solution.py exists in the repository root.")
return None
def load_session_cookie():
"""
Load the session cookie from .env/session_cookie file.
Returns:
The session cookie as a string, or None if not found.
"""
# Get the script's directory (repository root)
repo_root = Path(__file__).parent
cookie_file = repo_root / ".env" / "session_cookie"
try:
return cookie_file.read_text().strip()
except FileNotFoundError:
print(f"β οΈ Warning: Session cookie not found at {cookie_file}")
print("Input will not be fetched automatically.")
return None
def fetch_puzzle_input(year, day, session_cookie):
"""
Fetch the puzzle input from the Advent of Code website.
Args:
year: The year (e.g., 2024)
day: The day number (e.g., 1-25)
session_cookie: The session cookie for authentication
Returns:
The puzzle input as a string, or None if the fetch fails
"""
if not session_cookie:
return None
url = f"https://adventofcode.com/{year}/day/{day}/input"
try:
print(f"π Fetching puzzle input from {url}...")
# Create a request with the session cookie
req = Request(url, headers={
'User-Agent': 'Mozilla/5.0',
'Cookie': f'session={session_cookie}'
})
# Create SSL context that doesn't verify certificates
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
response = urlopen(req, timeout=10, context=context)
puzzle_input = response.read().decode('utf-8')
print(f"β
Successfully fetched puzzle input ({len(puzzle_input)} characters)")
return puzzle_input
except HTTPError as e:
if e.code == 404:
print(f"β οΈ Input not available. Day {day} may not be released yet for {year}.")
elif e.code == 400:
print(f"β οΈ Session cookie may be invalid or expired.")
else:
print(f"β οΈ HTTP Error {e.code}: {e.reason}")
return None
except URLError as e:
print(f"β οΈ Network error: {e.reason}")
return None
except Exception as e:
print(f"β οΈ Error fetching puzzle input: {e}")
return None
def fetch_problem_name(year, day):
"""
Fetch the problem name from the Advent of Code website.
Args:
year: The year (e.g., 2024)
day: The day number (e.g., 1-25)
Returns:
The problem name with spaces removed (e.g., "RedNosedReports")
or None if the fetch fails
"""
url = f"https://adventofcode.com/{year}/day/{day}"
try:
print(f"π Fetching problem name from {url}...")
# Create a request with a user agent to avoid being blocked
req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
# Create SSL context that doesn't verify certificates
# This handles environments where Python doesn't have access to system certs
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
response = urlopen(req, timeout=10, context=context)
html = response.read().decode('utf-8')
# Parse the title from the HTML
# The format is typically: <h2>--- Day X: Problem Name ---</h2>
match = re.search(r'<h2>---\s*Day\s*\d+:\s*(.+?)\s*---</h2>', html)
if match:
problem_title = match.group(1).strip()
# Remove spaces and special characters to create the directory name
problem_name = ''.join(problem_title.split())
# Remove any remaining special characters except alphanumeric
problem_name = re.sub(r'[^a-zA-Z0-9]', '', problem_name)
print(f"β
Found problem: {problem_title}")
return problem_name
else:
print("β οΈ Could not parse problem name from the page")
return None
except HTTPError as e:
if e.code == 404:
print(f"β Problem not found. Day {day} may not be released yet for {year}.")
else:
print(f"β HTTP Error {e.code}: {e.reason}")
return None
except URLError as e:
print(f"β Network error: {e.reason}")
return None
except Exception as e:
print(f"β Error fetching problem name: {e}")
return None
def create_problem_directory(year, day, problem_name, template_content, puzzle_input):
"""
Create a new Advent of Code problem directory with solution and input files.
Args:
year: The year (e.g., 2024)
day: The day number (e.g., 1-25)
problem_name: The name of the problem (e.g., TobogganTrajectory)
template_content: The content of the solution template
puzzle_input: The puzzle input content (or None for empty file)
"""
# Get the script's directory (repository root)
repo_root = Path(__file__).parent
# Create directory path: {year}/{day}-{problem_name}/
problem_dir = repo_root / str(year) / f"{day}-{problem_name}"
# Check if directory already exists
if problem_dir.exists():
print(f"β Error: Directory already exists: {problem_dir}")
return False
# Create the directory
problem_dir.mkdir(parents=True, exist_ok=True)
print(f"β
Created directory: {problem_dir}")
# Create solution.py from template
solution_file = problem_dir / "solution.py"
solution_file.write_text(template_content)
print(f"β
Created solution file: {solution_file}")
# Create input.txt with puzzle input or empty
input_file = problem_dir / "input.txt"
input_file.write_text(puzzle_input if puzzle_input else "")
if puzzle_input:
print(f"β
Created input file with puzzle input: {input_file}")
else:
print(f"β
Created empty input file: {input_file}")
print(f"\nπ Ready to solve! Open {problem_dir} and start coding!")
return True
def main():
if len(sys.argv) < 3 or len(sys.argv) > 4:
print("Usage: python new_problem.py <year> <day> [problem_name]")
print("\nExamples:")
print(" python new_problem.py 2024 3 # Automatically fetch problem name")
print(" python new_problem.py 2024 3 TobogganTrajectory # Manually specify problem name")
sys.exit(1)
year = sys.argv[1]
day = sys.argv[2]
problem_name = sys.argv[3] if len(sys.argv) == 4 else None
# Validate inputs
try:
year_int = int(year)
day_int = int(day)
if not (2015 <= year_int <= 2030):
print("β Error: Year should be between 2015 and 2030")
sys.exit(1)
if not (1 <= day_int <= 25):
print("β Error: Day should be between 1 and 25")
sys.exit(1)
except ValueError:
print("β Error: Year and day must be numbers")
sys.exit(1)
# Load the template
template_content = load_template()
if not template_content:
sys.exit(1)
# Load session cookie for fetching input
session_cookie = load_session_cookie()
# Fetch problem name if not provided
if not problem_name:
problem_name = fetch_problem_name(year, day)
if not problem_name:
print("\nβ Failed to fetch problem name automatically.")
print("Please provide the problem name manually:")
print(f" python new_problem.py {year} {day} <problem_name>")
sys.exit(1)
if not problem_name:
print("β Error: Problem name cannot be empty")
sys.exit(1)
# Fetch puzzle input
puzzle_input = fetch_puzzle_input(year, day, session_cookie)
# Create the problem directory
success = create_problem_directory(year, day, problem_name, template_content, puzzle_input)
if success:
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()