chefmate 1.0.0__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.
@@ -0,0 +1,1536 @@
1
+ # ------------------------------------------------------------ Welcome to ChefMate --------------------------------------------------------
2
+
3
+ # ---------------- Necessary Imports -------------------------------
4
+ import os
5
+ import sys
6
+ import time
7
+ import click
8
+ import platform
9
+ import colorama
10
+ import inquirer
11
+ import traceback
12
+ import subprocess
13
+ import pandas as pd
14
+ from rich import box
15
+ from rich.text import Text
16
+ from rich.table import Table
17
+ from rich.panel import Panel
18
+ from rich.align import Align
19
+ from rich.console import Console
20
+ from rich.progress import Progress
21
+ from tabulate import tabulate
22
+ from colorama import Fore, Style
23
+ from InquirerPy import inquirer
24
+ from chefmate.config import Config
25
+ from InquirerPy.base.control import Choice
26
+ from selenium.webdriver.common.by import By
27
+ from selenium.webdriver.common.keys import Keys
28
+ from chefmate.browser_manager import BrowserManager
29
+ from selenium.webdriver.support.ui import WebDriverWait
30
+ from selenium.webdriver.support import expected_conditions as EC
31
+ # ------------------------------------------------------------------
32
+
33
+ # Initialize colorama
34
+ colorama.init(autoreset=True)
35
+ console = Console()
36
+
37
+ # Language mapping
38
+ LANGUAGE_VALUES = {
39
+ "C++": "C++", "Python3": "PYTH 3", "Python 3": "PYTH 3",
40
+ "C": "C", "Java": "JAVA", "PyPy3": "PYPY3", "C#": "C#",
41
+ "JavaScript": "NODEJS", "Go": "GO", "TypeScript": "TS",
42
+ "PHP": "PHP", "Kotlin": "KTLN", "Rust": "rust", "R": "R"
43
+ }
44
+
45
+ # Language extensions mapping
46
+ LANGUAGE_EXTENSIONS = {
47
+ "C++": ".cpp", "Python3": ".py", "Python 3": ".py",
48
+ "C": ".c", "Java": ".java", "PyPy3": ".py", "C#": ".cs",
49
+ "JavaScript": ".js", "Go": ".go", "TypeScript": ".ts",
50
+ "PHP": ".php", "Kotlin": ".kt", "Rust": ".rs", "R": ".r"
51
+ }
52
+
53
+ # Default paths
54
+ DEFAULT_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".chefmate")
55
+ DEFAULT_CONFIG_FILE = os.path.join(DEFAULT_CONFIG_DIR, "config.json")
56
+
57
+ class ChefMate:
58
+ """Main ChefMate class for CodeChef automation"""
59
+
60
+ def hide_cursor(self):
61
+ sys.stdout.write("\033[?25l")
62
+ sys.stdout.flush()
63
+
64
+ def show_cursor(self):
65
+ sys.stdout.write("\033[?25h")
66
+ sys.stdout.flush()
67
+
68
+ def render_problem_tracker(self):
69
+ table = Table(
70
+ show_header=True,
71
+ header_style="bold magenta",
72
+ box=box.SIMPLE_HEAVY,
73
+ expand=False, # Prevent full-width stretching
74
+ )
75
+
76
+ table.add_column("Status", justify="center", width=7)
77
+ table.add_column("Problem", justify="left", style="bold")
78
+
79
+ symbol = {"pending": "🟡", "done": "✅", "wrong": "❌"}
80
+
81
+ for code, status in self.problem_status.items():
82
+ emoji = symbol.get(status, "❔")
83
+ table.add_row(emoji, code)
84
+
85
+ # Center the table itself
86
+ centered_table = Align.center(table, vertical="middle")
87
+
88
+ # Now return the panel with a fixed width
89
+ panel = Panel(centered_table, title="Problem Tracker", border_style="bold white", width=30)
90
+ return panel
91
+
92
+ def show_tracker(self):
93
+ print()
94
+ console.print(self.render_problem_tracker())
95
+ print()
96
+
97
+ def flush_stdin(self):
98
+ if platform.system() != "Windows":
99
+ import termios
100
+ try:
101
+ termios.tcflush(sys.stdin, termios.TCIFLUSH)
102
+ except:
103
+ pass
104
+ else:
105
+ import msvcrt
106
+ try:
107
+ while msvcrt.kbhit():
108
+ msvcrt.getch()
109
+ except:
110
+ pass
111
+
112
+ def ask(self, text):
113
+ return click.prompt(f" {Fore.MAGENTA}{text}")
114
+
115
+ def type_writer(self, text):
116
+ try:
117
+ self.hide_cursor()
118
+ for char in text:
119
+ sys.stdout.write(Fore.YELLOW + char)
120
+ sys.stdout.flush()
121
+ time.sleep(0.04)
122
+ sys.stdout.write("\n")
123
+ finally: self.show_cursor()
124
+
125
+ def goto_dashboard(self):
126
+ self.browser.driver.get("https://www.codechef.com/dashboard")
127
+ WebDriverWait(self.browser.driver, 10).until(EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo')]")))
128
+ print(f"{Fore.GREEN} Dashboard opened successfully! \u2714\n")
129
+
130
+ def display_logo(self):
131
+ banner = Text()
132
+ logo = f"""
133
+ {Fore.RED}
134
+ ╔═══════════════════════════════════════════════════════════════════════════════════╗
135
+ ║ ║
136
+ ║ ██████╗ ██╗ ██╗ ███████╗ ███████╗ ███╗ ███╗ █████╗ ████████╗ ███████╗ ║
137
+ ║ ██╔════╝ ██║ ██║ ██╔════╝ ██╔════╝ ████╗ ████║ ██╔══██╗ ╚══██╔══╝ ██╔════╝ ║
138
+ ║ ██║ ███████║ █████╗ █████╗ ██╔████╔██║ ███████║ ██║ █████╗ ║
139
+ ║ ██║ ██╔══██║ ██╔══╝ ██╔══╝ ██║╚██╔╝██║ ██╔══██║ ██║ ██╔══╝ ║
140
+ ║ ╚██████╗ ██║ ██║ ███████╗ ██║ ██║ ╚═╝ ██║ ██║ ██║ ██║ ███████╗ ║
141
+ ║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ║
142
+ ║ ║
143
+ ║═══════════════════════════════════════════════════════════════════════════════════║
144
+ ║ ║
145
+ ╚═══════════════════════════════════════════════════════════════════════════════════╝
146
+ {Style.RESET_ALL}
147
+ """
148
+
149
+ banner.append(logo)
150
+ # Print logo immediately
151
+ console.print(banner)
152
+
153
+ # Typewriter effect for the last line
154
+ tagline = "Automation tool for CodeChef"
155
+ up_lines, right_spaces = 4, 28
156
+
157
+ sys.stdout.write(f"\033[{up_lines}A")
158
+ sys.stdout.write(f"\033[{right_spaces}C")
159
+
160
+ try:
161
+ self.hide_cursor()
162
+ for char in tagline:
163
+ sys.stdout.write(Fore.RED + char + Style.RESET_ALL)
164
+ sys.stdout.flush()
165
+ time.sleep(0.04)
166
+
167
+ sys.stdout.flush()
168
+ print('\n' * 2)
169
+ finally: self.show_cursor()
170
+
171
+ def __init__(self, config_file=DEFAULT_CONFIG_FILE):
172
+ self.config = Config(config_file)
173
+ self.browser = BrowserManager(self.config)
174
+ self.problems, self.tabs = [], []
175
+ self.contest_id = None # Add this line to store the contest ID
176
+ self.contest_dir = None # Add this line to store the contest directory
177
+ self.problem_status = {}
178
+
179
+ def setup(self):
180
+ """Setup ChefMate configuration"""
181
+ username = click.prompt(f"{Fore.MAGENTA} Username", default=self.config.get("username", ""))
182
+ password = click.prompt(f"{Fore.MAGENTA} Password", hide_input=True, default=self.config.get("password", ""))
183
+ solution_path = click.prompt(f"{Fore.MAGENTA} Default solution file path", default=self.config.get("solution_path", ""))
184
+ chrome_user_data_dir = click.prompt(f"{Fore.MAGENTA} Chrome user data directory (optional)", default=self.config.get("chrome_user_data_dir", ""))
185
+ chrome_profile = click.prompt(f"{Fore.MAGENTA} Chrome profile name (e.g., Default, Profile 1, etc.) (optional)", default=self.config.get("chrome_profile", "Default"))
186
+
187
+ # Language Selections Here ------------------------------
188
+ lang_list = [
189
+ Choice(value="C++", name="C++"), Choice(value="Python 3", name="PYTH 3"), Choice(value="JAVA", name="Java"),
190
+ Choice(value="C", name="C"), Choice(value="PYPY3", name="PyPy3"), Choice(value="NODEJS", name="JavaScript"),
191
+ Choice(value="C#", name="C#"), Choice(value="GO", name="Go"), Choice(value="PHP", name="PHP"),
192
+ Choice(value="KTLN", name="Kotlin"), Choice(value="rust", name="Rust"),
193
+ Choice(value="R", name="R"), Choice(value="TS", name="TypeScript")
194
+ ]
195
+ default_lang = "C++"
196
+
197
+ lang_selector = inquirer.select(
198
+ message="Choose your preferred language: ",
199
+ choices=lang_list,
200
+ default=default_lang, # Use the stored current choice
201
+ qmark="", # Remove the default [?]
202
+ pointer=">", # Custom arrow pointer
203
+ instruction="(Use arrow keys to navigate)"
204
+ ).execute()
205
+
206
+ default_lang = lang_selector
207
+ print("\033[F\033[2K", end="")
208
+ print(f" You Chose: {Fore.CYAN}{default_lang}\n")
209
+
210
+ opt_list = [
211
+ Choice(value="default", name="Notepad (Default)"),
212
+ Choice(value="vscode", name="VS Code"),
213
+ Choice(value="sublime", name="Sublime Text"),
214
+ Choice(value="notepad++", name="Notepad++ (Windows)"),
215
+ Choice(value="atom", name="Atom"),
216
+ ]
217
+
218
+ editor = "default"
219
+
220
+ choice = inquirer.select(
221
+ message="Choose your preferred editor installed in your system: ",
222
+ choices=opt_list,
223
+ default=editor, # Use the stored current choice
224
+ qmark="", # Remove the default [?]
225
+ pointer=">", # Custom arrow pointer
226
+ instruction="(Use arrow keys to navigate)"
227
+ ).execute()
228
+
229
+ editor = choice
230
+
231
+ print("\033[F\033[2K", end="")
232
+ selected_opt = next((c.name for c in opt_list if c.value == choice), choice)
233
+ print(f" You Chose: {Fore.CYAN}{selected_opt}\n")
234
+
235
+ if choice == 1: editor = "default"
236
+ elif choice == 2: editor = "vscode"
237
+ elif choice == 3: editor = "sublime"
238
+ elif choice == 4: editor = "notepad++"
239
+ elif choice == 5: editor = "atom"
240
+
241
+ self.config.update_config(
242
+ username=username,
243
+ password=password,
244
+ preferred_language=default_lang,
245
+ solution_path=solution_path,
246
+ chrome_user_data_dir=chrome_user_data_dir,
247
+ preferred_editor=editor,
248
+ chrome_profile=chrome_profile,
249
+ )
250
+
251
+ print(f"{Fore.GREEN} Configuration saved successfully! \u2714\n")
252
+
253
+ def reconfig(self):
254
+ """Reconfigure ChefMate"""
255
+
256
+ choice = 1
257
+
258
+ change_list = [
259
+ Choice(value=1, name="Everything"),
260
+ Choice(value=2, name="Username"),
261
+ Choice(value=3, name="Password"),
262
+ Choice(value=4, name="Preferred Language"),
263
+ Choice(value=5, name="Solution File Path"),
264
+ Choice(value=6, name="Chrome User Data Directory"),
265
+ Choice(value=7, name="Preferred Editor"),
266
+ Choice(value=8, name="Chrome Profile"),
267
+ ]
268
+
269
+ choice_selector = inquirer.select(
270
+ message="What Changes do you want to make: ",
271
+ choices=change_list,
272
+ default=choice, # Use the stored current choice
273
+ qmark="", # Remove the default [?]
274
+ pointer=">", # Custom arrow pointer
275
+ instruction="(Use arrow keys to navigate)"
276
+ ).execute()
277
+
278
+ choice = choice_selector
279
+ print("\033[F\033[2K", end="")
280
+ selected_opt = next((c.name for c in change_list if c.value == choice_selector), choice_selector)
281
+ print(f" You Chose: {Fore.CYAN}{selected_opt}\n")
282
+
283
+ if choice == 1: self.setup()
284
+ elif choice == 2: self.config.set("username", click.prompt(f"{Fore.MAGENTA} New Username", default=self.config.get("username", "")))
285
+ elif choice == 3: self.config.set("password", click.prompt(f"{Fore.MAGENTA} New Password", hide_input=True, default=self.config.get("password", "")))
286
+ elif choice == 4:
287
+ lang_list = [
288
+ Choice(value="C++", name="C++"), Choice(value="Python 3", name="PYTH 3"), Choice(value="JAVA", name="Java"),
289
+ Choice(value="C", name="C"), Choice(value="PYPY3", name="PyPy3"), Choice(value="NODEJS", name="JavaScript"),
290
+ Choice(value="C#", name="C#"), Choice(value="GO", name="Go"), Choice(value="PHP", name="PHP"),
291
+ Choice(value="KTLN", name="Kotlin"), Choice(value="rust", name="Rust"),
292
+ Choice(value="R", name="R"), Choice(value="TS", name="TypeScript")
293
+ ]
294
+ default_lang = "C++"
295
+
296
+ lang_selector = inquirer.select(
297
+ message="Choose your preferred language: ",
298
+ choices=lang_list,
299
+ default=default_lang, # Use the stored current choice
300
+ qmark="", # Remove the default [?]
301
+ pointer=">", # Custom arrow pointer
302
+ instruction="(Use arrow keys to navigate)"
303
+ ).execute()
304
+
305
+ print("\033[F\033[2K", end="")
306
+ print(f" You Chose: {Fore.CYAN}{lang_selector}\n")
307
+
308
+ self.config.set("preferred_language", lang_selector)
309
+ elif choice == 5: self.config.set("solution_path", click.prompt(f"{Fore.MAGENTA} New Solution File Path", default=self.config.get("solution_path", "")))
310
+ elif choice == 6: self.config.set("chrome_user_data_dir", click.prompt(f"{Fore.MAGENTA} New Chrome User Data Directory", default=self.config.get("chrome_user_data_dir", "")))
311
+ elif choice == 7:
312
+ editors_list = [
313
+ Choice(value="default", name="Notepad (System Default)"),
314
+ Choice(value="vscode", name="VS Code"),
315
+ Choice(value="sublime", name="Sublime Text"),
316
+ Choice(value="notepad++", name="Notepad++ (Windows)"),
317
+ Choice(value="atom", name="Atom"),
318
+ ]
319
+
320
+ editor = "default"
321
+
322
+ editor_selector = inquirer.select(
323
+ message="Choose your preferred editor installed in your system: ",
324
+ choices=editors_list,
325
+ default=editor, # Use the stored current choice
326
+ qmark="", # Remove the default [?]
327
+ pointer=">", # Custom arrow pointer
328
+ instruction="(Use arrow keys to navigate)"
329
+ ).execute()
330
+
331
+ editor = editor_selector
332
+ print("\033[F\033[2K", end="")
333
+ selected_opt = next((c.name for c in editors_list if c.value == editor_selector), editor_selector)
334
+ print(f" You Chose: {Fore.CYAN}{selected_opt}")
335
+
336
+ self.config.set("preferred_editor", editor)
337
+ elif choice == 8:
338
+ self.config.set("chrome_profile", click.prompt(f"{Fore.MAGENTA} New profile name (e.g., Default, Profile 1, etc.) (optional)", default=self.config.get("chrome_profile", "Default")))
339
+ print(f"{Fore.GREEN} Configuration updated successfully! \u2714\n")
340
+
341
+ def initialize(self):
342
+ """Initialize browser and start session"""
343
+ try:
344
+ return self.browser.initialize_driver()
345
+ except Exception as e:
346
+ print(f"{Fore.RED} Close all instances of chrome running in the background\n")
347
+ return False
348
+
349
+ # ------------------- Login and Logout Process -------------------
350
+ def already_logged_in(self) -> bool:
351
+ """Check if already logged in to CodeChef"""
352
+ dashboard_URL = "https://www.codechef.com/dashboard"
353
+ self.browser.driver.get(dashboard_URL)
354
+ try:
355
+ WebDriverWait(self.browser.driver, 10).until(
356
+ EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo') ]"))
357
+ )
358
+ return self.browser.driver.current_url == dashboard_URL
359
+ except: return False
360
+
361
+ def login(self, first_login: bool = False):
362
+ """Login to CodeChef"""
363
+ if not self.browser.driver:
364
+ if not self.initialize():
365
+ print(f"{Fore.RED} Failed to initialize browser.\n")
366
+ return False
367
+
368
+ if self.already_logged_in():
369
+ print(f"{Fore.GREEN} Already logged in \u2714\n")
370
+ return True
371
+
372
+ username = self.config.get("username", "")
373
+ password = self.config.get("password", "")
374
+
375
+ if not username or not password:
376
+ username = self.ask("Username: ")
377
+ password = self.ask("Password: ")
378
+ save = click.confirm(" Save credentials for future use?", default=True)
379
+ if save:
380
+ self.config.update_config(username=username, password=password)
381
+
382
+ self.browser.driver.get("https://www.codechef.com/login")
383
+ self.type_writer(" Loggin in ... ")
384
+
385
+ WebDriverWait(self.browser.driver, 10).until(
386
+ EC.element_to_be_clickable((By.ID, "edit-name"))
387
+ )
388
+
389
+ if first_login:
390
+ self.browser.driver.refresh()
391
+ WebDriverWait(self.browser.driver, 10).until(
392
+ EC.element_to_be_clickable((By.ID, "edit-name"))
393
+ )
394
+
395
+ u_box = self.browser.driver.find_element(By.ID, "edit-name")
396
+ u_box.send_keys(Keys.CONTROL + "a" + Keys.BACKSPACE)
397
+ u_box.send_keys(username)
398
+
399
+ p_box = self.browser.driver.find_element(By.ID, "edit-pass")
400
+ p_box.send_keys(Keys.CONTROL + "a" + Keys.BACKSPACE)
401
+ p_box.send_keys(password + Keys.RETURN)
402
+
403
+ max_wait_time = 20
404
+ interval = 2
405
+ elapsed_time = 0
406
+
407
+ # For slow networks, retry login
408
+ while elapsed_time < max_wait_time:
409
+ try:
410
+ WebDriverWait(self.browser.driver, 5).until(
411
+ EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, '_content__container')]")
412
+ ))
413
+ print(f"{Fore.GREEN} Successfully loaded Dashboard \u2714\n{Style.RESET_ALL}")
414
+ return True
415
+ except:
416
+ elapsed_time += interval
417
+ print(f"{Fore.YELLOW} Waiting for Dashboard to load... {elapsed_time}/{max_wait_time} sec")
418
+ time.sleep(interval)
419
+
420
+ print(f"{Fore.RED} Login timed out. Please check your internet connection and try again.\n")
421
+ return False
422
+
423
+ def logout(self):
424
+ """Logout from CodeChef"""
425
+ if not self.browser.driver:
426
+ print(f"{Fore.YELLOW} No active browser session.\n")
427
+ return
428
+
429
+ self.type_writer(" Logging out ... ")
430
+
431
+ self.browser.driver.get("https://www.codechef.com/logout")
432
+ try:
433
+ WebDriverWait(self.browser.driver, 10).until(
434
+ EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo')]"))
435
+ )
436
+ print(f"{Fore.GREEN} Logged out successfully \u2714\n")
437
+ except:
438
+ print(f"{Fore.YELLOW} Logout status unclear, closing browser anyway.\n")
439
+
440
+ def quit(self):
441
+ """Quit the browser and end session"""
442
+ if self.browser.driver:
443
+ self.browser.close()
444
+ print(f"{Fore.GREEN} Browser closed successfully! \u2714\n")
445
+ else:
446
+ print(f"{Fore.YELLOW} No active browser session to close.\n")
447
+ # ----------------------------------------------------------------
448
+
449
+ # ----------------- Problem Scraping, Opening in Tabs and Submitting Solutions --------
450
+ def open_contest(self):
451
+ """
452
+ Prompts for contest ID and division, opens contest page and all problem tabs.
453
+ """
454
+
455
+ if not self.already_logged_in():
456
+ self.login()
457
+
458
+ division_map = {1: 'A', 2: 'B', 3: 'C', 4: 'D'}
459
+
460
+ contest_id = click.prompt(f" {Fore.MAGENTA}Enter contest ID (e.g., 171)", type=int)
461
+ division = click.prompt(f" {Fore.MAGENTA}Enter your division (1-4)", type=click.Choice(['1', '2', '3', '4']))
462
+ division = int(division)
463
+
464
+ # Store the contest ID for future use
465
+ self.contest_id = str(contest_id)
466
+
467
+ contest_url = f"https://www.codechef.com/START{contest_id}{division_map[division]}"
468
+ print(f"{Fore.CYAN} Opening link: {contest_url}{Style.RESET_ALL}")
469
+ self.type_writer(" Opening contest page ... ")
470
+
471
+ self.browser.driver.get(contest_url)
472
+ try:
473
+ WebDriverWait(self.browser.driver, 10).until(
474
+ EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, 'problem-table')]"))
475
+ )
476
+ except:
477
+ print(f"{Fore.YELLOW} Contest page looks different than expected, but continuing...")
478
+
479
+ base_url = "https://www.codechef.com"
480
+ self.problems = []
481
+ try:
482
+ table = WebDriverWait(self.browser.driver, 10).until(
483
+ EC.presence_of_element_located((By.CLASS_NAME, "dataTable"))
484
+ )
485
+ links = table.find_elements(By.TAG_NAME, "a")
486
+ except:
487
+ links = self.browser.driver.find_elements(By.XPATH, "//a[contains(@href, 'problems')]")
488
+
489
+ for link in links:
490
+ href = link.get_attribute('href')
491
+ if href and "/problems/" in href:
492
+ if not href.startswith(base_url):
493
+ href = base_url + href if href.startswith("/") else f"{base_url}/{href}"
494
+ code = href.split("/")[-1]
495
+ title = link.text.strip() or "Untitled Problem"
496
+ self.problems.append((code, title, href))
497
+
498
+ if not self.problems:
499
+ print(f"{Fore.RED} No problems found in this contest! \u2716")
500
+ return False
501
+
502
+ self.problem_status = {code: "pending" for code, _, _ in self.problems}
503
+
504
+ self.tabs = [self.browser.driver.current_window_handle]
505
+ with Progress() as progress:
506
+ task = progress.add_task("[yellow] Opening problem tabs", total=len(self.problems))
507
+ for _, _, url in self.problems:
508
+ self.browser.driver.execute_script("window.open(arguments[0]);", url)
509
+ WebDriverWait(self.browser.driver, 2).until(lambda d: len(d.window_handles) > len(self.tabs))
510
+ self.tabs.append(self.browser.driver.window_handles[-1])
511
+ progress.update(task, advance=1)
512
+
513
+ # Display problems
514
+ print(f"\n{Fore.GREEN} Found {len(self.problems)} problems:")
515
+ for idx, (code, title, _) in enumerate(self.problems, start=1):
516
+ print(f" {idx}. {code}: {title}")
517
+
518
+ print()
519
+ self.browser.driver.switch_to.window(self.tabs[0])
520
+ return True
521
+
522
+ def _get_solution_path(self):
523
+ """Get and validate solution file path"""
524
+ solution_path = self.config.get("solution_path", "")
525
+
526
+ if not solution_path or not os.path.exists(solution_path):
527
+ solution_path = click.prompt("Enter the path to your solution file")
528
+ save_path = click.confirm("Save this path for future use?", default=True)
529
+ if save_path:
530
+ self.config.set("solution_path", solution_path)
531
+
532
+ if not os.path.exists(solution_path):
533
+ print(f"{Fore.RED}Solution file not found: {solution_path}")
534
+ return None
535
+ if not os.path.isfile(solution_path):
536
+ print(f"{Fore.RED}Solution path is not a file: {solution_path}")
537
+ return None
538
+ if not os.access(solution_path, os.R_OK):
539
+ print(f"{Fore.RED}Solution file is not readable: {solution_path}")
540
+ return None
541
+ if os.path.getsize(solution_path) == 0:
542
+ print(f"{Fore.RED}Solution file is empty: {solution_path}")
543
+ return None
544
+ if not solution_path.endswith(('.cpp', '.py', '.java', '.js', '.go', '.php', '.kt', '.rs', '.c++', '.cs', '.rb', '.r', '.c', '.ts')):
545
+ print(f"{Fore.RED}Unsupported file type: {solution_path}")
546
+ return None
547
+
548
+ return solution_path
549
+
550
+ def _select_language(self):
551
+ """Select language in CodeChef UI"""
552
+ language = self.config.get("preferred_language", "")
553
+
554
+ if not language:
555
+ language = self.ask("Enter your preferred language (e.g., C++): ")
556
+ save_lang = click.confirm("Save this language preference?", default=True)
557
+ if save_lang:
558
+ self.config.set("preferred_language", language)
559
+
560
+ data_value = LANGUAGE_VALUES.get(language)
561
+ if not data_value:
562
+ print(f" {Fore.RED}Language {language} not supported.")
563
+ return False
564
+
565
+ try:
566
+ dropdown = WebDriverWait(self.browser.driver, 10).until(
567
+ EC.element_to_be_clickable((By.CSS_SELECTOR, "div[id^='language-select']"))
568
+ )
569
+ dropdown.click()
570
+ option = WebDriverWait(self.browser.driver, 10).until(
571
+ EC.element_to_be_clickable((By.CSS_SELECTOR, f"li[data-value='{data_value}']"))
572
+ )
573
+ option.click()
574
+ return True
575
+ except Exception as e:
576
+ print(f"{Fore.RED} Error selecting language: {e}")
577
+ return False
578
+
579
+ def _load_code(self, code_text):
580
+ """Load code into editor"""
581
+ try:
582
+ text_input = WebDriverWait(self.browser.driver, 20).until(
583
+ EC.presence_of_element_located((By.CSS_SELECTOR, "textarea.ace_text-input"))
584
+ )
585
+ self.browser.driver.execute_script("arguments[0].focus();", text_input)
586
+ WebDriverWait(self.browser.driver, 1).until(
587
+ EC.presence_of_element_located((By.CSS_SELECTOR, "textarea.ace_text-input:focus"))
588
+ )
589
+ text_input.send_keys(Keys.CONTROL + 'a')
590
+ text_input.send_keys(Keys.DELETE)
591
+ self.browser.driver.execute_script(
592
+ "ace.edit(document.querySelector('.ace_editor')).setValue(arguments[0], -1);",
593
+ code_text
594
+ )
595
+ return True
596
+ except Exception as e:
597
+ print(f"{Fore.RED} Error loading code: {e}")
598
+ return False
599
+
600
+ def verdict(self):
601
+ # Whether your solution was CORRECT or NOT ??
602
+ sol_verd = self.browser.driver.find_element(By.XPATH, "//div[contains(@class, '_run__container')]").text
603
+ sol_verd = sol_verd[0]
604
+ return sol_verd.lower() == 'c'
605
+
606
+ def submit_solution(self, problem_num: int = None):
607
+ # Submit solution for a problem
608
+
609
+ curr_url = self.browser.driver.current_url
610
+ if not self.login():
611
+ self.login()
612
+
613
+ self.browser.driver.get(curr_url)
614
+
615
+ if not self.problems or not self.tabs:
616
+ print(f"{Fore.RED} No problems loaded. Please open contest tab first.")
617
+ return False
618
+
619
+ if problem_num is None:
620
+ problem_num = click.prompt(f"{Fore.MAGENTA} Enter problem number to submit", type=int, default=1)
621
+
622
+ if problem_num < 1 or problem_num > len(self.problems):
623
+ print(f"{Fore.RED} Invalid problem number. Please enter a valid number (1-{len(self.problems)}).")
624
+ return False
625
+
626
+ # Switch to selected problem tab
627
+ self.browser.driver.switch_to.window(self.tabs[problem_num])
628
+
629
+ if not self._select_language():
630
+ return False
631
+
632
+ # Try to find solution file created by solve() function
633
+ solution_path = self._find_solution_file(problem_num)
634
+
635
+ # If solution file not found, prompt for path
636
+ if not solution_path:
637
+ solution_path = self._get_solution_path()
638
+
639
+ if not solution_path:
640
+ return False
641
+
642
+ try:
643
+ with open(solution_path, 'r') as f:
644
+ code_text = f.read()
645
+ print(f"{Fore.GREEN} Using solution file: {solution_path}")
646
+ except Exception as e:
647
+ print(f"{Fore.RED} Error reading solution file: {e}")
648
+ return False
649
+
650
+ if not self._load_code(code_text):
651
+ return False
652
+
653
+ # Submit the Code
654
+ try:
655
+ text_input = self.browser.driver.find_element(By.CSS_SELECTOR, "textarea.ace_text-input")
656
+ text_input.send_keys(Keys.CONTROL + Keys.ENTER)
657
+ self.type_writer(" Submitting the Code ... ")
658
+
659
+ WebDriverWait(self.browser.driver, 20).until(
660
+ EC.presence_of_element_located((By.XPATH, "//div[contains(@class, '_run__container')]"))
661
+ )
662
+
663
+ verdict_in_run_container = self.browser.driver.find_element(By.XPATH, "//div[contains(@class, '_run__container')]").text
664
+ if 'Error' in verdict_in_run_container or 'limit' in verdict_in_run_container.lower():
665
+ print(f"{Fore.RED} {verdict_in_run_container}\n")
666
+ return False
667
+
668
+ # Wait for the verdict table to appear
669
+ WebDriverWait(self.browser.driver, 20).until(
670
+ EC.presence_of_element_located((By.XPATH, "//table[contains(@class, 'status-table')]"))
671
+ )
672
+
673
+ # Extract table data
674
+ table_element = self.browser.driver.find_element(By.XPATH, "//table[contains(@class, 'status-table')]")
675
+ rows = table_element.find_elements(By.TAG_NAME, "tr")
676
+
677
+ # Parse the table data
678
+ table_data = []
679
+ for row in rows:
680
+ cells = row.find_elements(By.TAG_NAME, "td")
681
+ if cells:
682
+ row_data = [cell.text for cell in cells]
683
+ table_data.append(row_data)
684
+
685
+ final_verdict = table_data.pop()
686
+
687
+ final_table_data = []
688
+ for data in table_data:
689
+ interm = []
690
+ if 'Correct' in data[-1]:
691
+ for i in range(0, 3):
692
+ if i != 2: interm.append(f"{Fore.GREEN}{data[i]}{Style.RESET_ALL}")
693
+ else:
694
+ string = ''
695
+ for char in data[i]:
696
+ if char != '\n': string += f'{Fore.GREEN}{char}{Style.RESET_ALL}'
697
+ else: string += f'{char}'
698
+ interm.append(string)
699
+ elif 'Wrong Answer' in data[-1]:
700
+ for i in range(0, 3):
701
+ if i != 2: interm.append(f"{Fore.RED}{data[i]}{Style.RESET_ALL}")
702
+ else:
703
+ string = ''
704
+ for char in data[i]:
705
+ if char != '\n': string += f'{Fore.RED}{char}{Style.RESET_ALL}'
706
+ else: string += f'{char}'
707
+ interm.append(string)
708
+ else:
709
+ for i in data:
710
+ interm.append(f"{i}")
711
+ final_table_data.append(interm)
712
+
713
+ # Create a pandas DataFrame
714
+ if 'Correct' in final_verdict[1]:
715
+ df = pd.DataFrame(final_table_data, columns=[f"{Fore.GREEN}Sub-Task{Style.RESET_ALL}",
716
+ f"{Fore.GREEN}Task #{Style.RESET_ALL}",
717
+ f"{Fore.GREEN}Result (time){Style.RESET_ALL}"])
718
+ else:
719
+ df = pd.DataFrame(final_table_data, columns=[f"{Fore.RED}Sub-Task{Style.RESET_ALL}",
720
+ f"{Fore.RED}Task #{Style.RESET_ALL}",
721
+ f"{Fore.RED}Result (time){Style.RESET_ALL}"])
722
+
723
+ print(f"\n{Fore.CYAN} Verdict Table:")
724
+ print(tabulate(
725
+ df,
726
+ headers='keys',
727
+ tablefmt='fancy_grid',
728
+ showindex=False,
729
+ stralign='center',
730
+ numalign='center',
731
+ colalign=('center', 'center', 'center')
732
+ ))
733
+ print(Style.RESET_ALL)
734
+
735
+ print(f" {Fore.YELLOW}{final_verdict[0]}")
736
+
737
+ code = self.problems[problem_num - 1][0]
738
+
739
+ if "Correct" in final_verdict[1]:
740
+ print(f"{Fore.GREEN} VERDICT: CORRECT {Fore.GREEN}\u2714\n{Style.RESET_ALL}")
741
+ self.problem_status[code] = "done"
742
+
743
+ elif "Wrong" in final_verdict[1]:
744
+ print(f"{Fore.RED} VERDICT: INCORRECT {Fore.RED}\u2716\n{Style.RESET_ALL}")
745
+ if self.problem_status.get(code) != "done": # Optional: don't overwrite if already done
746
+ self.problem_status[code] = "wrong"
747
+
748
+ else:
749
+ self.problem_status[code] = "pending"
750
+
751
+ return True
752
+ except Exception as e:
753
+ print(f"{Fore.RED} Error submitting code: {e}")
754
+ traceback.print_exc() # Print the full traceback for better debugging
755
+ return False
756
+ # -------------------------------------------------------------------------------------
757
+
758
+ # ------------------ Checker Functions ----------------------
759
+ def demo_cases_check(self, problem_num: int = None):
760
+ """
761
+ Runs all sample test cases for a problem, displays the outputs in a table,
762
+ then compares the outputs line-by-line (ignoring extra whitespace and case)
763
+ and prints a detailed verdict message inspired by verdict.py.
764
+ """
765
+ import re # For whitespace normalization
766
+
767
+ # ANSI color codes (from verdict.py)
768
+ GREEN_TICK = '\u2705'
769
+ RED_CR0SS = '\u274c'
770
+ BOLD_YELLOW = '\033[1;33m'
771
+ GREEN = '\033[0;32m'
772
+ RED = '\x1b[91m'
773
+ RESET = '\033[0m'
774
+
775
+ def normalize_whitespace(text):
776
+ return re.sub(r'\s+', ' ', text.strip())
777
+
778
+ if not self.problems or not self.tabs:
779
+ print(f"{Fore.RED} No problems loaded. Please open a contest page first.")
780
+ return False
781
+
782
+ if problem_num is None:
783
+ problem_num = click.prompt(f"{Fore.MAGENTA} Enter problem number to check", type=int, default=1)
784
+
785
+ if problem_num < 1 or problem_num > len(self.problems):
786
+ print(f"{Fore.RED} Invalid problem number. Please enter a valid number (1-{len(self.problems)}).")
787
+ return False
788
+
789
+ # Switch to the appropriate problem tab and select language
790
+ self.browser.driver.switch_to.window(self.tabs[problem_num])
791
+ if not self._select_language():
792
+ return False
793
+
794
+ # Locate solution file (from solve() or prompt)
795
+ solution_path = self._find_solution_file(problem_num)
796
+ if not solution_path:
797
+ solution_path = self._get_solution_path()
798
+ if not solution_path:
799
+ return False
800
+
801
+ try:
802
+ with open(solution_path, 'r') as f:
803
+ code_text = f.read()
804
+ print(f"{Fore.GREEN} Using solution file: {solution_path}")
805
+ except Exception as e:
806
+ print(f"{Fore.RED} Error reading solution file: {e}")
807
+ return False
808
+ if not self._load_code(code_text):
809
+ return False
810
+
811
+ self.type_writer(" Checking Demo test cases ...")
812
+
813
+ # Run test cases by sending keystrokes
814
+ try:
815
+ text_input = self.browser.driver.find_element(By.CSS_SELECTOR, "textarea.ace_text-input")
816
+ text_input.send_keys(Keys.CONTROL + Keys.SHIFT + Keys.ENTER)
817
+ WebDriverWait(self.browser.driver, 20).until(
818
+ EC.presence_of_element_located((By.XPATH, "//div[contains(@class, '_output-item__value_')]"))
819
+ )
820
+ except Exception as e:
821
+ print(f"{Fore.RED} Error running test cases: {e}")
822
+ return False
823
+
824
+ # Retrieve outputs
825
+ try:
826
+ actual_elem = self.browser.driver.find_elements(By.XPATH, "//div[contains(@class, '_output-item__value_')]")
827
+ actual_output_text = actual_elem[-1].text
828
+ expected_elem = self.browser.driver.find_elements(By.XPATH, "//div[contains(@class, '_values_')]")
829
+ expected_output_text = expected_elem[-1].text
830
+ except Exception as e:
831
+ print(f"{Fore.RED} Error retrieving outputs: {e}")
832
+ return False
833
+
834
+ status_bar = self.browser.driver.find_element(By.XPATH, "//div[contains(@class, '_status__container_')]").text
835
+ status_text = str(status_bar.split(":")[-1].strip())
836
+ if 'error' in status_text or 'limit' in status_text:
837
+ print(f"{Fore.RED} {status_text} {RESET}\n")
838
+ return False
839
+
840
+ full_text = expected_elem[0].text.strip()
841
+ expected_output_text = expected_elem[-1].text.strip()
842
+
843
+ if expected_output_text and expected_output_text in full_text:
844
+ input_text = full_text.replace(expected_output_text, '').strip()
845
+ else:
846
+ input_text = full_text.strip()
847
+
848
+ if actual_output_text.strip() == input_text:
849
+ print(f"{Fore.YELLOW} NO OUTPUT{RESET}\n")
850
+ return False
851
+
852
+ # Pre-check: Time Limit Exceeded and no-output conditions
853
+ normalized_actual_full = normalize_whitespace(actual_output_text)
854
+ normalized_expected_full = normalize_whitespace(expected_output_text)
855
+ if "time limit exceeded" in normalized_actual_full.lower():
856
+ print(f"{BOLD_YELLOW} VERDICT:{RESET} {RED}TIME LIMIT EXCEEDED {RED_CR0SS}{RESET}\n")
857
+ return True
858
+ if not normalized_actual_full and normalized_expected_full:
859
+ print(f"{BOLD_YELLOW} VERDICT:{RESET} {RED}TIME LIMIT EXCEEDED ⏰{RESET}\n")
860
+ return True
861
+
862
+ # Split outputs into lines and normalize each line for comparison
863
+ actual_lines = [normalize_whitespace(line) for line in actual_output_text.splitlines()]
864
+ expected_lines = [normalize_whitespace(line) for line in expected_output_text.splitlines()]
865
+
866
+ # Ensure both lists have the same length by padding with empty strings
867
+ max_len = max(len(actual_lines), len(expected_lines))
868
+ actual_lines.extend([''] * (max_len - len(actual_lines)))
869
+ expected_lines.extend([''] * (max_len - len(expected_lines)))
870
+
871
+ # Build a table to display outputs
872
+ headers = ['Your Output', 'Expected Output']
873
+ table_rows = []
874
+ for u, e in zip(actual_lines, expected_lines):
875
+ # Color entire rows in green if they match, otherwise highlight differences
876
+ if u.lower() == e.lower():
877
+ table_rows.append([f"{GREEN}{u}{RESET}", f"{GREEN}{e}{RESET}"])
878
+ else:
879
+ table_rows.append([f"{RED}{u}{RESET}", f"{GREEN}{e}{RESET}"])
880
+ print()
881
+ print(tabulate(
882
+ table_rows,
883
+ headers=headers,
884
+ tablefmt='fancy_grid',
885
+ showindex=False,
886
+ stralign='center',
887
+ numalign='center',
888
+ colalign=('center', 'center')
889
+ ))
890
+ print()
891
+
892
+ # Line-by-line comparison to detect the first mismatch
893
+ mismatch_found = False
894
+ for i, (exp, act) in enumerate(zip(expected_lines, actual_lines), start=1):
895
+ if exp.lower() != act.lower():
896
+ print(f"{BOLD_YELLOW} VERDICT:{RESET} {RED}WRONG ANSWER {RED_CR0SS}{RESET}\n")
897
+ print(f"{RED} INCORRECT AT LINE: {i}")
898
+ print(f"{GREEN} EXPECTED: {GREEN}{exp}{RESET}")
899
+ print(f"{BOLD_YELLOW} FOUND: {RED}{act}{RESET}\n")
900
+ mismatch_found = True
901
+ break
902
+ if not mismatch_found and len(actual_lines) == len(expected_lines):
903
+ print(f"{BOLD_YELLOW} VERDICT:{RESET} {GREEN}CORRECT {GREEN_TICK}{RESET}\n")
904
+ elif not mismatch_found:
905
+ # If line counts differ, treat as mismatch
906
+ print(f"{BOLD_YELLOW} VERDICT:{RESET} {RED}WRONG ANSWER {RED_CR0SS}{RESET}\n")
907
+ if len(expected_lines) > len(actual_lines):
908
+ print(f"{RED} INCORRECT AT LINE: {len(actual_lines) + 1}")
909
+ print(f"{GREEN} EXPECTED: {GREEN}{expected_lines[len(actual_lines)]}{RESET}")
910
+ print(f"{BOLD_YELLOW} FOUND: {RED}NULL{RESET}\n")
911
+ else:
912
+ print(f"{RED} INCORRECT AT LINE: {len(expected_lines) + 1}")
913
+ print(f"{GREEN} EXPECTED: {GREEN}NULL{RESET}")
914
+ print(f"{BOLD_YELLOW} FOUND: {RED}{actual_lines[len(expected_lines)]}{RESET}\n")
915
+ return True
916
+
917
+ def solve(self):
918
+ """
919
+ Create solution files for problems and open selected problem for editing
920
+ """
921
+ if not self.problems or not self.tabs:
922
+ print(f"{Fore.RED} No problems loaded. Please open a contest first.")
923
+ return False
924
+
925
+ # Get solution directory from config
926
+ solution_path = self.config.get("solution_path", "")
927
+ if not solution_path:
928
+ solution_path = click.prompt(" Enter solution directory path")
929
+ save_path = click.confirm(" Save this path for future use?", default=True)
930
+ if save_path:
931
+ self.config.set("solution_path", solution_path)
932
+
933
+ self.type_writer(" Creating Directories ... ")
934
+
935
+ # Create solution directory if it doesn't exist
936
+ if not os.path.exists(solution_path):
937
+ try:
938
+ os.makedirs(solution_path)
939
+ print(f"{Fore.GREEN} Created solution directory: {solution_path}")
940
+ except Exception as e:
941
+ print(f"{Fore.RED} Error creating solution directory: {e}")
942
+ return False
943
+
944
+ # Get preferred language
945
+ language = self.config.get("preferred_language", "")
946
+ if not language:
947
+ language = click.prompt(" Enter your preferred language (e.g., C++)")
948
+ save_lang = click.confirm(" Save this language preference?", default=True)
949
+ if save_lang:
950
+ self.config.set("preferred_language", language)
951
+
952
+ # Get file extension for the language
953
+ extension = LANGUAGE_EXTENSIONS.get(language, ".txt")
954
+ if extension == ".txt":
955
+ print(f"{Fore.YELLOW} Warning: Unknown language '{language}'. Using .txt extension.")
956
+
957
+ # Use existing contest ID or extract contest ID from URL
958
+ if self.contest_id is None and self.tabs and self.browser.driver:
959
+ current_url = self.browser.driver.current_url
960
+ if "START" in current_url:
961
+ try:
962
+ # Extract the contest number from START{NUM}{DIVISION}
963
+ # For example: https://www.codechef.com/START171A -> 171
964
+ self.contest_id = ''.join(filter(str.isdigit, current_url.split("START")[1][:4]))
965
+ print(f"{Fore.GREEN} Detected contest ID: {self.contest_id}")
966
+ except:
967
+ self.contest_id = "unknown"
968
+ else:
969
+ self.contest_id = "unknown"
970
+
971
+ # Use existing contest directory or create a new one
972
+ if self.contest_dir and os.path.exists(self.contest_dir):
973
+ contest_dir = self.contest_dir
974
+ print(f"{Fore.GREEN} Using existing contest directory: {contest_dir}")
975
+ else:
976
+ # Create Starter directory with contest number if it doesn't exist
977
+ contest_dir = os.path.join(solution_path, f"Starter {self.contest_id}")
978
+
979
+ # Check if the directory already exists
980
+ if os.path.exists(contest_dir):
981
+ print(f"{Fore.GREEN} Using existing contest directory: {contest_dir}")
982
+ else:
983
+ try:
984
+ os.makedirs(contest_dir)
985
+ print(f"{Fore.GREEN} Created contest directory: {contest_dir}")
986
+ except Exception as e:
987
+ print(f"{Fore.RED} Error creating contest directory: {e}")
988
+ return False
989
+
990
+ # Store the contest directory for future use
991
+ self.contest_dir = contest_dir
992
+
993
+ # Save contest directory in config for other functions to use
994
+ self.config.set("current_contest_dir", contest_dir)
995
+
996
+ # Get username for author comment
997
+ ur_name = self.config.get("username", "USER")
998
+
999
+ # Create template file content based on language
1000
+ template_content = ""
1001
+ if language == "C++":
1002
+ template_content = f"""// Author -> '{ur_name}'
1003
+
1004
+ #include <bits/stdc++.h>
1005
+ using namespace std;
1006
+
1007
+ int main() {{
1008
+ ios_base::sync_with_stdio(false);
1009
+ cin.tie(NULL);
1010
+
1011
+ // Your code here
1012
+
1013
+ return 0;
1014
+ }}
1015
+ """
1016
+ elif language == "Python3" or language == "Python 3" or language == "PyPy3":
1017
+ template_content = f"""# Author -> '{ur_name}'
1018
+
1019
+ def main():
1020
+ # Your code here
1021
+ pass
1022
+
1023
+ if __name__ == "__main__":
1024
+ main()
1025
+ """
1026
+ elif language == "Java":
1027
+ template_content = f"""// Author -> '{ur_name}'
1028
+ import java.util.*;
1029
+
1030
+ public class Main {{
1031
+ public static void main(String[] args) {{
1032
+ Scanner sc = new Scanner(System.in);
1033
+
1034
+ // Your code here
1035
+
1036
+ sc.close();
1037
+ }}
1038
+ }}
1039
+ """
1040
+ elif language == "JavaScript" or language == "NodeJS" or language == "NODEJS":
1041
+ template_content = f"""// Author -> '{ur_name}'
1042
+
1043
+ function main() {{
1044
+ // Your code here
1045
+ }}
1046
+
1047
+ main();
1048
+ """
1049
+ elif language == "Go":
1050
+ template_content = f"""// Author -> '{ur_name}'
1051
+ package main
1052
+
1053
+ import (
1054
+ "fmt"
1055
+ )
1056
+
1057
+ func main() {{
1058
+ // Your code here
1059
+ }}
1060
+ """
1061
+ elif language == "C#" or language == "CS":
1062
+ template_content = f"""// Author -> '{ur_name}'
1063
+ using System;
1064
+
1065
+ class Program {{
1066
+ static void Main(string[] args) {{
1067
+ // Your code here
1068
+ }}
1069
+ }}
1070
+ """
1071
+ elif language == "PHP":
1072
+ template_content = f"""<?php
1073
+ // Author -> '{ur_name}'
1074
+
1075
+ function main() {{
1076
+ // Your code here
1077
+ }}
1078
+
1079
+ main();
1080
+ ?>
1081
+ """
1082
+ elif language == "Ruby":
1083
+ template_content = f"""# Author -> '{ur_name}'
1084
+
1085
+ def main
1086
+ # Your code here
1087
+ end
1088
+
1089
+ main
1090
+ """
1091
+ elif language == "Rust":
1092
+ template_content = f"""// Author -> '{ur_name}'
1093
+
1094
+ fn main() {{
1095
+ // Your code here
1096
+ }}
1097
+ """
1098
+ elif language == "TypeScript" or language == "TS":
1099
+ template_content = f"""// Author -> '{ur_name}'
1100
+
1101
+ function main(): void {{
1102
+ // Your code here
1103
+ }}
1104
+
1105
+ main();
1106
+ """
1107
+ elif language == "Kotlin" or language == "KTLN":
1108
+ template_content = f"""// Author -> '{ur_name}'
1109
+
1110
+ fun main() {{
1111
+ // Your code here
1112
+ }}
1113
+ """
1114
+ elif language == "R":
1115
+ template_content = f"""# Author -> '{ur_name}'
1116
+
1117
+ main <- function() {{
1118
+ # Your code here
1119
+ }}
1120
+
1121
+ main()
1122
+ """
1123
+ elif language == "C":
1124
+ template_content = f"""// Author -> '{ur_name}'
1125
+
1126
+ #include <stdio.h>
1127
+
1128
+ int main() {{
1129
+ // Your code here
1130
+ return 0;
1131
+ }}
1132
+ """
1133
+ else:
1134
+ template_content = f"""// Author -> '{ur_name}'
1135
+
1136
+ // Your code here
1137
+ """
1138
+
1139
+ # Create solution files for each problem (only if they don't exist)
1140
+ created_files = []
1141
+ existing_files = []
1142
+
1143
+ for idx, (code, _, _) in enumerate(self.problems, start=1):
1144
+ filename = f"{code}{extension}"
1145
+ filepath = os.path.join(contest_dir, filename)
1146
+
1147
+ # Check if file already exists
1148
+ if os.path.exists(filepath):
1149
+ existing_files.append((idx, filepath))
1150
+ else:
1151
+ try:
1152
+ with open(filepath, 'w') as f:
1153
+ f.write(template_content)
1154
+ print(f"{Fore.GREEN} Created solution file: {filepath}")
1155
+ created_files.append((idx, filepath))
1156
+ except Exception as e:
1157
+ print(f"{Fore.RED} Error creating solution file: {filepath}: {e}")
1158
+
1159
+ # Combine created and existing files (sorted by index)
1160
+ all_files = sorted(created_files + existing_files, key=lambda x: x[0])
1161
+
1162
+ # Display files
1163
+ if created_files:
1164
+ print(f"\n{Fore.GREEN} New solution files created for {len(created_files)} problems:")
1165
+ for idx, filepath in created_files:
1166
+ print(f" {idx}. {os.path.basename(filepath)}")
1167
+
1168
+ if existing_files:
1169
+ print(f"\n{Fore.CYAN} Using existing solution files for {len(existing_files)} problems:")
1170
+ for idx, filepath in existing_files:
1171
+ print(f" {idx}. {os.path.basename(filepath)}")
1172
+
1173
+ # Select problem to solve
1174
+ print(f"\n{Fore.CYAN} Available problems:")
1175
+ for idx, (code, title, _) in enumerate(self.problems, start=1):
1176
+ print(f" {idx}. {code}: {title}")
1177
+
1178
+ print()
1179
+ problem_num = click.prompt(f"{Fore.MAGENTA} Enter problem number to solve", type=int, default=1)
1180
+ print()
1181
+ if problem_num < 1 or problem_num > len(self.problems):
1182
+ print(f"{Fore.RED} Invalid problem number. Please enter a valid number (1-{len(self.problems)}).")
1183
+ return False
1184
+
1185
+ # Find the file for the selected problem
1186
+ selected_file = None
1187
+ for idx, filepath in all_files:
1188
+ if idx == problem_num:
1189
+ selected_file = filepath
1190
+ break
1191
+
1192
+ if not selected_file:
1193
+ print(f"{Fore.RED} No solution file found for problem {problem_num}.")
1194
+ return False
1195
+
1196
+ # Open selected problem file
1197
+ try:
1198
+ # Switch to problem tab first
1199
+ self.browser.driver.switch_to.window(self.tabs[problem_num])
1200
+ print(f"{Fore.GREEN} Switched to problem tab: {self.problems[problem_num-1][1]}")
1201
+
1202
+ # Check if file exists
1203
+ if not os.path.exists(selected_file):
1204
+ print(f"{Fore.RED} File not found: {selected_file}")
1205
+ return False
1206
+
1207
+ # Get preferred editor from config or prompt
1208
+ editor = self.config.get("preferred_editor", "")
1209
+ if not editor:
1210
+ choice = 1
1211
+ editor = [
1212
+ Choice(value="default", name="Notepad (System Default)"),
1213
+ Choice(value="vscode", name="VS Code"),
1214
+ Choice(value="sublime", name="Sublime Text"),
1215
+ Choice(value="notepad++", name="Notepad++ (Windows)"),
1216
+ Choice(value="atom", name="Atom"),
1217
+ ]
1218
+
1219
+ editor_selector = inquirer.select(
1220
+ message="Select your preferred editor:",
1221
+ choices=editor,
1222
+ default="default", # Use the stored current choice
1223
+ qmark="", # Remove the default [?]
1224
+ pointer=">", # Custom arrow pointer
1225
+ instruction="(Use arrow keys to navigate)"
1226
+ ).execute()
1227
+
1228
+ choice = editor_selector
1229
+ selected_editor = next((c.name for c in editor if c.value == editor_selector), editor_selector)
1230
+ print(f"{Fore.GREEN} You chose: {selected_editor}")
1231
+
1232
+ if choice == 1:
1233
+ editor = "default"
1234
+ elif choice == 2:
1235
+ editor = "vscode"
1236
+ elif choice == 3:
1237
+ editor = "sublime"
1238
+ elif choice == 4:
1239
+ editor = "notepad++"
1240
+ elif choice == 5:
1241
+ editor = "atom"
1242
+ elif choice == 6:
1243
+ editor = click.prompt(" Enter editor command (use {file} as placeholder for filename)")
1244
+
1245
+ save_choice = click.confirm(" Save this editor preference?", default=True)
1246
+ if save_choice:
1247
+ self.config.set("preferred_editor", editor)
1248
+
1249
+ # Open file with selected editor
1250
+ print(f"{Fore.GREEN} Opening file with editor: {editor}")
1251
+
1252
+ devnull = subprocess.DEVNULL
1253
+
1254
+ try:
1255
+ if editor == "default":
1256
+ # Use system default
1257
+ if os.name == 'nt': # Windows
1258
+ subprocess.Popen(['notepad', selected_file],
1259
+ start_new_session=True,
1260
+ shell=True,
1261
+ stdout=devnull,
1262
+ stderr=devnull)
1263
+ time.sleep(0.3)
1264
+ self.flush_stdin()
1265
+ elif os.name == 'posix': # Linux/Mac
1266
+ if sys.platform == 'darwin': # Mac
1267
+ subprocess.call(['open', selected_file], stdout=devnull, stderr=devnull)
1268
+ time.sleep(0.3)
1269
+ self.flush_stdin()
1270
+ else: # Linux
1271
+ subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1272
+ time.sleep(0.3)
1273
+ self.flush_stdin()
1274
+ elif editor == "vscode":
1275
+ # Check for different VS Code executable names
1276
+ vscode_commands = ['code', 'Code.exe', 'code.cmd', 'Code.cmd']
1277
+ success = False
1278
+ for cmd in vscode_commands:
1279
+ try:
1280
+ subprocess.Popen([cmd, selected_file],
1281
+ start_new_session=True,
1282
+ shell=(os.name == 'nt'),
1283
+ stdout=devnull,
1284
+ stderr=devnull)
1285
+ time.sleep(0.3)
1286
+ self.flush_stdin()
1287
+ success = True
1288
+ break
1289
+ except FileNotFoundError:
1290
+ continue
1291
+
1292
+ if not success:
1293
+ print(f"{Fore.YELLOW} VS Code not found in PATH. Falling back to system default.")
1294
+ print(f"{Fore.YELLOW} TIP - Add the path of vscode.exe file into the system's environment variable path.")
1295
+ # Fall back to system default
1296
+ if os.name == 'nt':
1297
+ subprocess.Popen(['notepad', selected_file],
1298
+ start_new_session=True,
1299
+ shell=True,
1300
+ stdout=devnull,
1301
+ stderr=devnull)
1302
+ time.sleep(0.3)
1303
+ self.flush_stdin()
1304
+ elif os.name == 'posix':
1305
+ if sys.platform == 'darwin':
1306
+ subprocess.call(['open', selected_file], stdout=devnull, stderr=devnull)
1307
+ time.sleep(0.3)
1308
+ self.flush_stdin()
1309
+ else:
1310
+ subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1311
+ time.sleep(0.3)
1312
+ self.flush_stdin()
1313
+ elif editor == "sublime":
1314
+ # Try different possible Sublime Text command names
1315
+ sublime_commands = ['subl', 'sublime_text', 'sublime', 'Sublime Text.exe']
1316
+ success = False
1317
+ for cmd in sublime_commands:
1318
+ try:
1319
+ subprocess.Popen([cmd, selected_file],
1320
+ start_new_session=True,
1321
+ shell=(os.name == 'nt'),
1322
+ stdout=devnull,
1323
+ stderr=devnull)
1324
+ time.sleep(0.3)
1325
+ self.flush_stdin()
1326
+ success = True
1327
+ break
1328
+ except FileNotFoundError:
1329
+ continue
1330
+
1331
+ if not success:
1332
+ print(f"{Fore.YELLOW} Sublime Text not found in PATH. Falling back to system default.")
1333
+ print(f"{Fore.YELLOW} TIP - Add the path of sublime_text.exe file into the system's environment variable path.")
1334
+ # Fall back to system default
1335
+ if os.name == 'nt':
1336
+ subprocess.Popen(['notepad', selected_file],
1337
+ start_new_session=True,
1338
+ shell=True,
1339
+ stdout=devnull,
1340
+ stderr=devnull)
1341
+ time.sleep(0.3)
1342
+ self.flush_stdin()
1343
+ else:
1344
+ subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1345
+ time.sleep(0.3)
1346
+ self.flush_stdin()
1347
+ elif editor == "notepad++":
1348
+ try:
1349
+ # Try different possible Notepad++ executable names
1350
+ npp_commands = ['notepad++', 'notepad++.exe', 'Notepad++.exe']
1351
+ success = False
1352
+ for cmd in npp_commands:
1353
+ try:
1354
+ subprocess.Popen([cmd, selected_file],
1355
+ start_new_session=True,
1356
+ shell=(os.name == 'nt'),
1357
+ stdout=devnull,
1358
+ stderr=devnull)
1359
+ time.sleep(0.3)
1360
+ self.flush_stdin()
1361
+ success = True
1362
+ break
1363
+ except FileNotFoundError:
1364
+ continue
1365
+
1366
+ if not success:
1367
+ print(f"{Fore.YELLOW} Notepad++ not found in PATH. Falling back to system notepad.")
1368
+ print(f"{Fore.YELLOW} TIP - Add the path of notepad++.exe file into the system's environment variable path.")
1369
+ print(f"{Fore.YELLOW} Till then Falling Back to the regulare notepad on Windows.")
1370
+ # Fall back to regular notepad on Windows
1371
+ subprocess.Popen(['notepad', selected_file],
1372
+ start_new_session=True,
1373
+ shell=True,
1374
+ stdout=devnull,
1375
+ stderr=devnull)
1376
+ time.sleep(0.3)
1377
+ self.flush_stdin()
1378
+ except:
1379
+ # If all else fails, use system default
1380
+ subprocess.Popen(['notepad', selected_file],
1381
+ start_new_session=True,
1382
+ shell=True,
1383
+ stdout=devnull,
1384
+ stderr=devnull) if os.name == 'nt' else subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1385
+ time.sleep(0.3)
1386
+ self.flush_stdin()
1387
+ elif editor == "atom":
1388
+ success = False
1389
+ try:
1390
+ subprocess.Popen(['atom', selected_file],
1391
+ start_new_session=True,
1392
+ shell=(os.name == 'nt'),
1393
+ stdout=devnull,
1394
+ stderr=devnull)
1395
+ time.sleep(0.3)
1396
+ self.flush_stdin()
1397
+ success = True
1398
+ except FileNotFoundError:
1399
+ print(f"{Fore.RED} File not found")
1400
+
1401
+ if not success:
1402
+ print(f"{Fore.YELLOW} Atom not found in PATH. Falling back to system default.")
1403
+ print(f"{Fore.YELLOW} TIP - Add the path of atom.exe file into the system's environment variable path.\n")
1404
+ # Fall back to system default
1405
+ if os.name == 'nt':
1406
+ subprocess.Popen(['notepad', selected_file],
1407
+ start_new_session=True,
1408
+ shell=True,
1409
+ stdout=devnull,
1410
+ stderr=devnull)
1411
+ time.sleep(0.3)
1412
+ self.flush_stdin()
1413
+ else:
1414
+ subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1415
+ time.sleep(0.3)
1416
+ self.flush_stdin()
1417
+ else:
1418
+ # Custom command
1419
+ success = False
1420
+ try:
1421
+ subprocess.Popen(['notepad', selected_file],
1422
+ start_new_session=True,
1423
+ shell=True,
1424
+ stdout=devnull,
1425
+ stderr=devnull)
1426
+ time.sleep(0.3)
1427
+ self.flush_stdin()
1428
+ success = True
1429
+ except FileNotFoundError:
1430
+ print(f"{Fore.RED} File not found ")
1431
+ except Exception as e:
1432
+ print(f"{Fore.YELLOW} Error executing custom command. Falling back to system default.")
1433
+ # Fall back to system default
1434
+ if os.name == 'nt':
1435
+ subprocess.Popen(['notepad', selected_file],
1436
+ start_new_session=True,
1437
+ shell=True,
1438
+ stdout=devnull,
1439
+ stderr=devnull)
1440
+ time.sleep(0.3)
1441
+ self.flush_stdin()
1442
+ success = True
1443
+ else:
1444
+ subprocess.call(['xdg-open', selected_file], stdout=devnull, stderr=devnull)
1445
+ time.sleep(0.3)
1446
+ self.flush_stdin()
1447
+ success = True
1448
+
1449
+ print(f"{Fore.GREEN} Opened file for editing: {selected_file}\n")
1450
+ return True
1451
+ except Exception as e:
1452
+ print(f"{Fore.RED} Error opening file: {e}")
1453
+ print(f"{Fore.YELLOW} Attempting to open with system default editor instead...")
1454
+ try:
1455
+ # Last resort - try with system default
1456
+ if os.name == 'nt': # Windows
1457
+ subprocess.Popen(['notepad', selected_file],
1458
+ start_new_session=True,
1459
+ shell=True,
1460
+ stdout=devnull,
1461
+ stderr=devnull)
1462
+ time.sleep(0.3)
1463
+ self.flush_stdin()
1464
+ elif os.name == 'posix': # Linux/Mac
1465
+ if sys.platform == 'darwin': # Mac
1466
+ subprocess.call(['open', selected_file], stdout=devnull, stderr=devnull)
1467
+ time.sleep(0.3)
1468
+ self.flush_stdin()
1469
+ else: # Linux
1470
+ subprocess.call(['xdg-open', selected_file])
1471
+ time.sleep(0.3)
1472
+ self.flush_stdin()
1473
+ print(f"{Fore.GREEN} Opened file with system default editor: {selected_file}\n")
1474
+ return True
1475
+ except Exception as e2:
1476
+ print(f"{Fore.RED} All attempts to open the file failed: {e2}")
1477
+ print(f"{Fore.YELLOW} Please manually open the file: {selected_file}\n")
1478
+ return False
1479
+ except Exception as e:
1480
+ print(f"{Fore.RED} Error opening file: {e}\n")
1481
+ return False
1482
+
1483
+ def _find_solution_file(self, problem_num):
1484
+ """Find solution file for the given problem number"""
1485
+ if not problem_num or problem_num < 1 or problem_num > len(self.problems):
1486
+ return None
1487
+
1488
+ # Get problem code for the selected problem
1489
+ problem_code = self.problems[problem_num-1][0]
1490
+
1491
+ # First try to use the stored contest directory
1492
+ if self.contest_dir and os.path.exists(self.contest_dir):
1493
+ contest_dir = self.contest_dir
1494
+ else:
1495
+ # Get contest directory from config (set by solve function)
1496
+ contest_dir = self.config.get("current_contest_dir", "")
1497
+
1498
+ # If contest directory not found, try to determine it
1499
+ if not contest_dir or not os.path.exists(contest_dir):
1500
+ solution_path = self.config.get("solution_path", "")
1501
+ if not solution_path or not os.path.exists(solution_path):
1502
+ return None
1503
+
1504
+ # Try to extract contest ID from URL or use stored contest ID
1505
+ contest_id = self.contest_id
1506
+ if not contest_id:
1507
+ contest_id = "unknown"
1508
+ if self.tabs and self.browser.driver:
1509
+ current_url = self.browser.driver.current_url
1510
+ if "START" in current_url:
1511
+ try:
1512
+ contest_id = ''.join(filter(str.isdigit, current_url.split("START")[1][:4]))
1513
+ except:
1514
+ pass
1515
+
1516
+ contest_dir = os.path.join(solution_path, f"Starter {contest_id}")
1517
+ if not os.path.exists(contest_dir):
1518
+ return None
1519
+
1520
+ # Get preferred language extension
1521
+ extension = LANGUAGE_EXTENSIONS.get(self.config.get("preferred_language", ""), "")
1522
+
1523
+ # Try to find file with preferred extension
1524
+ if extension:
1525
+ filepath = os.path.join(contest_dir, f"{problem_code}{extension}")
1526
+ if os.path.exists(filepath):
1527
+ return filepath
1528
+
1529
+ # If not found or no extension specified, try all possible extensions
1530
+ for ext in LANGUAGE_EXTENSIONS.values():
1531
+ filepath = os.path.join(contest_dir, f"{problem_code}{ext}")
1532
+ if os.path.exists(filepath):
1533
+ return filepath
1534
+
1535
+ return None
1536
+ # ----------------------------------------------------------