chefmate 1.0.0__py3-none-any.whl → 1.0.2__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.
- chefmate/chefmate_core.py +81 -31
- chefmate/cli.py +25 -93
- chefmate-1.0.2.dist-info/METADATA +136 -0
- chefmate-1.0.2.dist-info/RECORD +10 -0
- {chefmate-1.0.0.dist-info → chefmate-1.0.2.dist-info}/licenses/LICENSE +1 -1
- chefmate-1.0.0.dist-info/METADATA +0 -313
- chefmate-1.0.0.dist-info/RECORD +0 -11
- chefmate-1.0.0.dist-info/entry_points.txt +0 -2
- {chefmate-1.0.0.dist-info → chefmate-1.0.2.dist-info}/WHEEL +0 -0
- {chefmate-1.0.0.dist-info → chefmate-1.0.2.dist-info}/top_level.txt +0 -0
chefmate/chefmate_core.py
CHANGED
@@ -27,6 +27,7 @@ from selenium.webdriver.common.by import By
|
|
27
27
|
from selenium.webdriver.common.keys import Keys
|
28
28
|
from chefmate.browser_manager import BrowserManager
|
29
29
|
from selenium.webdriver.support.ui import WebDriverWait
|
30
|
+
from selenium.common.exceptions import TimeoutException
|
30
31
|
from selenium.webdriver.support import expected_conditions as EC
|
31
32
|
# ------------------------------------------------------------------
|
32
33
|
|
@@ -124,7 +125,12 @@ class ChefMate:
|
|
124
125
|
|
125
126
|
def goto_dashboard(self):
|
126
127
|
self.browser.driver.get("https://www.codechef.com/dashboard")
|
127
|
-
|
128
|
+
try:
|
129
|
+
WebDriverWait(self.browser.driver, 15).until(
|
130
|
+
EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo')]"))
|
131
|
+
)
|
132
|
+
except TimeoutException:
|
133
|
+
print(f"{Fore.RED} Failed to load dashboard page in 15seconds. Slow Internet \n")
|
128
134
|
print(f"{Fore.GREEN} Dashboard opened successfully! \u2714\n")
|
129
135
|
|
130
136
|
def display_logo(self):
|
@@ -175,6 +181,7 @@ class ChefMate:
|
|
175
181
|
self.contest_id = None # Add this line to store the contest ID
|
176
182
|
self.contest_dir = None # Add this line to store the contest directory
|
177
183
|
self.problem_status = {}
|
184
|
+
self.logged_in = False
|
178
185
|
|
179
186
|
def setup(self):
|
180
187
|
"""Setup ChefMate configuration"""
|
@@ -352,10 +359,13 @@ class ChefMate:
|
|
352
359
|
dashboard_URL = "https://www.codechef.com/dashboard"
|
353
360
|
self.browser.driver.get(dashboard_URL)
|
354
361
|
try:
|
355
|
-
WebDriverWait(self.browser.driver,
|
362
|
+
WebDriverWait(self.browser.driver, 15).until(
|
356
363
|
EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo') ]"))
|
357
364
|
)
|
358
365
|
return self.browser.driver.current_url == dashboard_URL
|
366
|
+
except TimeoutException:
|
367
|
+
print(f"{Fore.RED} Failed to load dashboard page in 15seconds. Slow Internet \n")
|
368
|
+
return False
|
359
369
|
except: return False
|
360
370
|
|
361
371
|
def login(self, first_login: bool = False):
|
@@ -367,6 +377,7 @@ class ChefMate:
|
|
367
377
|
|
368
378
|
if self.already_logged_in():
|
369
379
|
print(f"{Fore.GREEN} Already logged in \u2714\n")
|
380
|
+
self.logged_in = True
|
370
381
|
return True
|
371
382
|
|
372
383
|
username = self.config.get("username", "")
|
@@ -382,15 +393,23 @@ class ChefMate:
|
|
382
393
|
self.browser.driver.get("https://www.codechef.com/login")
|
383
394
|
self.type_writer(" Loggin in ... ")
|
384
395
|
|
385
|
-
|
386
|
-
|
387
|
-
|
396
|
+
try:
|
397
|
+
WebDriverWait(self.browser.driver, 15).until(
|
398
|
+
EC.element_to_be_clickable((By.ID, "edit-name"))
|
399
|
+
)
|
400
|
+
except TimeoutException:
|
401
|
+
print(f"{Fore.RED} Failed to load login page in 15seconds. Slow Internet, Please Try again\n")
|
402
|
+
return False
|
388
403
|
|
389
404
|
if first_login:
|
390
405
|
self.browser.driver.refresh()
|
391
|
-
|
392
|
-
|
393
|
-
|
406
|
+
try:
|
407
|
+
WebDriverWait(self.browser.driver, 15).until(
|
408
|
+
EC.element_to_be_clickable((By.ID, "edit-name"))
|
409
|
+
)
|
410
|
+
except TimeoutException:
|
411
|
+
print(f"{Fore.RED} Failed to load login page in 15seconds. Slow Internet, Please Try again\n")
|
412
|
+
return False
|
394
413
|
|
395
414
|
u_box = self.browser.driver.find_element(By.ID, "edit-name")
|
396
415
|
u_box.send_keys(Keys.CONTROL + "a" + Keys.BACKSPACE)
|
@@ -401,7 +420,7 @@ class ChefMate:
|
|
401
420
|
p_box.send_keys(password + Keys.RETURN)
|
402
421
|
|
403
422
|
max_wait_time = 20
|
404
|
-
interval =
|
423
|
+
interval = 5
|
405
424
|
elapsed_time = 0
|
406
425
|
|
407
426
|
# For slow networks, retry login
|
@@ -411,6 +430,7 @@ class ChefMate:
|
|
411
430
|
EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, '_content__container')]")
|
412
431
|
))
|
413
432
|
print(f"{Fore.GREEN} Successfully loaded Dashboard \u2714\n{Style.RESET_ALL}")
|
433
|
+
self.logged_in = True
|
414
434
|
return True
|
415
435
|
except:
|
416
436
|
elapsed_time += interval
|
@@ -425,18 +445,25 @@ class ChefMate:
|
|
425
445
|
if not self.browser.driver:
|
426
446
|
print(f"{Fore.YELLOW} No active browser session.\n")
|
427
447
|
return
|
428
|
-
|
448
|
+
|
429
449
|
self.type_writer(" Logging out ... ")
|
430
450
|
|
431
451
|
self.browser.driver.get("https://www.codechef.com/logout")
|
432
452
|
try:
|
433
|
-
WebDriverWait(self.browser.driver,
|
453
|
+
WebDriverWait(self.browser.driver, 15).until(
|
434
454
|
EC.presence_of_element_located((By.XPATH, "//img[contains(@alt, 'CodeChef Logo')]"))
|
435
455
|
)
|
436
456
|
print(f"{Fore.GREEN} Logged out successfully \u2714\n")
|
457
|
+
self.logged_in = False
|
458
|
+
return
|
459
|
+
except TimeoutException:
|
460
|
+
print(f"{Fore.RED} Logout timed out, has waited 15 seconds. Please check your internet connection and try again.\n")
|
461
|
+
return
|
437
462
|
except:
|
438
463
|
print(f"{Fore.YELLOW} Logout status unclear, closing browser anyway.\n")
|
439
|
-
|
464
|
+
self.logged_in = False
|
465
|
+
return
|
466
|
+
|
440
467
|
def quit(self):
|
441
468
|
"""Quit the browser and end session"""
|
442
469
|
if self.browser.driver:
|
@@ -452,8 +479,10 @@ class ChefMate:
|
|
452
479
|
Prompts for contest ID and division, opens contest page and all problem tabs.
|
453
480
|
"""
|
454
481
|
|
455
|
-
if not self.
|
456
|
-
self.
|
482
|
+
if not self.logged_in:
|
483
|
+
self.type_writer(" Logging in before opening contest ... ")
|
484
|
+
if not self.login():
|
485
|
+
return False
|
457
486
|
|
458
487
|
division_map = {1: 'A', 2: 'B', 3: 'C', 4: 'D'}
|
459
488
|
|
@@ -470,9 +499,11 @@ class ChefMate:
|
|
470
499
|
|
471
500
|
self.browser.driver.get(contest_url)
|
472
501
|
try:
|
473
|
-
WebDriverWait(self.browser.driver,
|
502
|
+
WebDriverWait(self.browser.driver, 15).until(
|
474
503
|
EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, 'problem-table')]"))
|
475
504
|
)
|
505
|
+
except TimeoutException:
|
506
|
+
print(f"{Fore.RED} Failed to load problems in 15seconds. Slow Internet, Please Try again\n")
|
476
507
|
except:
|
477
508
|
print(f"{Fore.YELLOW} Contest page looks different than expected, but continuing...")
|
478
509
|
|
@@ -483,6 +514,8 @@ class ChefMate:
|
|
483
514
|
EC.presence_of_element_located((By.CLASS_NAME, "dataTable"))
|
484
515
|
)
|
485
516
|
links = table.find_elements(By.TAG_NAME, "a")
|
517
|
+
except TimeoutException:
|
518
|
+
print(f"{Fore.RED} Failed to load problems in 10seconds. Slow Internet, Please Try again\n")
|
486
519
|
except:
|
487
520
|
links = self.browser.driver.find_elements(By.XPATH, "//a[contains(@href, 'problems')]")
|
488
521
|
|
@@ -575,7 +608,7 @@ class ChefMate:
|
|
575
608
|
except Exception as e:
|
576
609
|
print(f"{Fore.RED} Error selecting language: {e}")
|
577
610
|
return False
|
578
|
-
|
611
|
+
|
579
612
|
def _load_code(self, code_text):
|
580
613
|
"""Load code into editor"""
|
581
614
|
try:
|
@@ -606,12 +639,6 @@ class ChefMate:
|
|
606
639
|
def submit_solution(self, problem_num: int = None):
|
607
640
|
# Submit solution for a problem
|
608
641
|
|
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
642
|
if not self.problems or not self.tabs:
|
616
643
|
print(f"{Fore.RED} No problems loaded. Please open contest tab first.")
|
617
644
|
return False
|
@@ -656,9 +683,18 @@ class ChefMate:
|
|
656
683
|
text_input.send_keys(Keys.CONTROL + Keys.ENTER)
|
657
684
|
self.type_writer(" Submitting the Code ... ")
|
658
685
|
|
659
|
-
|
660
|
-
|
661
|
-
|
686
|
+
try:
|
687
|
+
WebDriverWait(self.browser.driver, 20).until(
|
688
|
+
EC.presence_of_element_located((By.XPATH, "//div[contains(@class, '_run__container')]"))
|
689
|
+
)
|
690
|
+
except TimeoutException:
|
691
|
+
print(f"{Fore.RED} Submission timed out, has waited 20 seconds. Please check your internet connection and try again.\n")
|
692
|
+
return False
|
693
|
+
except Exception as e:
|
694
|
+
print(f"{Fore.RED} Error submitting code: {e}")
|
695
|
+
return False
|
696
|
+
|
697
|
+
# Wait for the verdict to appear
|
662
698
|
|
663
699
|
verdict_in_run_container = self.browser.driver.find_element(By.XPATH, "//div[contains(@class, '_run__container')]").text
|
664
700
|
if 'Error' in verdict_in_run_container or 'limit' in verdict_in_run_container.lower():
|
@@ -666,9 +702,16 @@ class ChefMate:
|
|
666
702
|
return False
|
667
703
|
|
668
704
|
# Wait for the verdict table to appear
|
669
|
-
|
670
|
-
|
671
|
-
|
705
|
+
try:
|
706
|
+
WebDriverWait(self.browser.driver, 20).until(
|
707
|
+
EC.presence_of_element_located((By.XPATH, "//table[contains(@class, 'status-table')]"))
|
708
|
+
)
|
709
|
+
except TimeoutException:
|
710
|
+
print(f"{Fore.RED} Submission timed out, has waited 20 seconds. Please check your internet connection and try again.\n")
|
711
|
+
return False
|
712
|
+
except Exception as e:
|
713
|
+
print(f"{Fore.RED} Error submitting code: {e}")
|
714
|
+
return False
|
672
715
|
|
673
716
|
# Extract table data
|
674
717
|
table_element = self.browser.driver.find_element(By.XPATH, "//table[contains(@class, 'status-table')]")
|
@@ -814,9 +857,16 @@ class ChefMate:
|
|
814
857
|
try:
|
815
858
|
text_input = self.browser.driver.find_element(By.CSS_SELECTOR, "textarea.ace_text-input")
|
816
859
|
text_input.send_keys(Keys.CONTROL + Keys.SHIFT + Keys.ENTER)
|
817
|
-
|
818
|
-
|
819
|
-
|
860
|
+
try:
|
861
|
+
WebDriverWait(self.browser.driver, 20).until(
|
862
|
+
EC.presence_of_element_located((By.XPATH, "//div[contains(@class, '_output-item__value_')]"))
|
863
|
+
)
|
864
|
+
except TimeoutException:
|
865
|
+
print(f"{Fore.RED} Timed out waiting for test cases to run.")
|
866
|
+
return False
|
867
|
+
except Exception as e:
|
868
|
+
print(f"{Fore.RED} Error waiting for test cases to run: {e}")
|
869
|
+
return False
|
820
870
|
except Exception as e:
|
821
871
|
print(f"{Fore.RED} Error running test cases: {e}")
|
822
872
|
return False
|
chefmate/cli.py
CHANGED
@@ -38,68 +38,13 @@ def loading_animation(stop_event):
|
|
38
38
|
def clear_terminal():
|
39
39
|
"""Clears the terminal screen based on the OS"""
|
40
40
|
os.system('cls' if platform.system() == 'Windows' else 'clear')
|
41
|
-
print(f"{Fore.GREEN} Terminal cleared successfully!
|
41
|
+
print(f"{Fore.GREEN} Terminal cleared successfully! ✔\n")
|
42
42
|
|
43
43
|
@click.group()
|
44
44
|
def cli():
|
45
45
|
"""ChefMate - CodeChef Automation Tool"""
|
46
46
|
pass
|
47
47
|
|
48
|
-
@cli.command()
|
49
|
-
def setup():
|
50
|
-
"""Configure ChefMate settings"""
|
51
|
-
cm = ChefMate()
|
52
|
-
cm.setup()
|
53
|
-
|
54
|
-
@cli.command()
|
55
|
-
def login():
|
56
|
-
"""Login to CodeChef"""
|
57
|
-
cm = ChefMate()
|
58
|
-
if cm.initialize():
|
59
|
-
cm.login()
|
60
|
-
else:
|
61
|
-
click.echo(f"{Fore.RED} Failed to initialize browser.")
|
62
|
-
|
63
|
-
@cli.command()
|
64
|
-
def logout():
|
65
|
-
"""Logout from CodeChef"""
|
66
|
-
cm = ChefMate()
|
67
|
-
if cm.initialize():
|
68
|
-
cm.logout()
|
69
|
-
else:
|
70
|
-
click.echo(f"{Fore.RED} Failed to initialize browser.")
|
71
|
-
|
72
|
-
@cli.command()
|
73
|
-
@click.option('--problem', '-p', type=int, help='Problem number to check')
|
74
|
-
def check(problem):
|
75
|
-
"""Check sample test cases for a problem"""
|
76
|
-
cm = ChefMate()
|
77
|
-
if cm.initialize() and cm.login():
|
78
|
-
if cm.open_contest():
|
79
|
-
cm.demo_cases_check(problem)
|
80
|
-
else:
|
81
|
-
click.echo(f"{Fore.RED} Failed to initialize session.")
|
82
|
-
|
83
|
-
@cli.command()
|
84
|
-
@click.option('--problem', '-p', type=int, help='Problem number to submit')
|
85
|
-
def submit(problem):
|
86
|
-
"""Submit solution for a problem"""
|
87
|
-
cm = ChefMate()
|
88
|
-
if cm.initialize() and cm.login():
|
89
|
-
if cm.open_contest():
|
90
|
-
cm.submit_solution(problem)
|
91
|
-
else:
|
92
|
-
click.echo(f"{Fore.RED} Failed to initialize session.")
|
93
|
-
|
94
|
-
@cli.command()
|
95
|
-
def contest():
|
96
|
-
"""Open a contest and load problems"""
|
97
|
-
cm = ChefMate()
|
98
|
-
if cm.initialize() and cm.login():
|
99
|
-
cm.open_contest()
|
100
|
-
else:
|
101
|
-
click.echo(f"{Fore.RED} Failed to initialize session.")
|
102
|
-
# -------------------- Interactive Mode ------------------
|
103
48
|
@cli.command()
|
104
49
|
def interactive():
|
105
50
|
"""Run ChefMate in interactive mode"""
|
@@ -161,31 +106,35 @@ def interactive():
|
|
161
106
|
choice = inquirer.select(
|
162
107
|
message="Select an option: ",
|
163
108
|
choices=quest_list,
|
164
|
-
default=current_choice,
|
165
|
-
qmark="",
|
166
|
-
pointer=">",
|
109
|
+
default=current_choice,
|
110
|
+
qmark="",
|
111
|
+
pointer=">",
|
167
112
|
instruction="(Use arrow keys to navigate)"
|
168
113
|
).execute()
|
169
114
|
|
170
|
-
# Update the current choice for next iteration
|
171
115
|
current_choice = choice
|
172
116
|
|
173
117
|
print("\033[F\033[2K", end="")
|
174
118
|
selected_name = next((c.name for c in quest_list if c.value == choice), choice)
|
175
119
|
table = Table(show_header=False, box=box.ROUNDED, border_style='green')
|
176
|
-
|
177
|
-
# Create a Text object with styling for selected name
|
178
120
|
styled_text = Text("You Selected: ")
|
179
121
|
styled_text.append(selected_name, style="cyan")
|
180
|
-
|
181
122
|
table.add_row(styled_text)
|
182
123
|
console.print(table)
|
183
124
|
|
184
125
|
if choice == '_login':
|
185
|
-
if first_login:
|
186
|
-
cm.login(first_login=True)
|
126
|
+
if first_login: cm.login(first_login=True)
|
187
127
|
else: cm.login()
|
188
|
-
elif choice == '_logout':
|
128
|
+
elif choice == '_logout':
|
129
|
+
if cm.tabs and len(cm.tabs) > 1:
|
130
|
+
cm.browser.driver.switch_to.window(cm.tabs[0])
|
131
|
+
for tab in cm.tabs[1:]:
|
132
|
+
cm.browser.driver.switch_to.window(tab)
|
133
|
+
cm.browser.driver.close()
|
134
|
+
cm.tabs = [cm.tabs[0]]
|
135
|
+
cm.browser.driver.switch_to.window(cm.tabs[0])
|
136
|
+
print(f"{Fore.GREEN} Closed contest tabs successfully! ✔")
|
137
|
+
cm.logout()
|
189
138
|
elif choice == '_config': cm.setup()
|
190
139
|
elif choice == '_clear': clear_terminal()
|
191
140
|
elif choice == '_contest_ini':
|
@@ -202,74 +151,57 @@ def interactive():
|
|
202
151
|
cm.quit()
|
203
152
|
break
|
204
153
|
elif choice == '_log_and_ex':
|
154
|
+
if cm.tabs and len(cm.tabs) > 1:
|
155
|
+
cm.browser.driver.switch_to.window(cm.tabs[0])
|
156
|
+
for tab in cm.tabs[1:]:
|
157
|
+
cm.browser.driver.switch_to.window(tab)
|
158
|
+
cm.browser.driver.close()
|
159
|
+
cm.tabs = [cm.tabs[0]]
|
160
|
+
cm.browser.driver.switch_to.window(cm.tabs[0])
|
161
|
+
print(f"{Fore.GREEN} All tabs closed successfully! ✔")
|
205
162
|
cm.logout()
|
206
163
|
cm.quit()
|
207
164
|
break
|
208
165
|
elif choice == '_close_curr_cont':
|
209
|
-
# Close all problem tabs
|
210
166
|
if cm.tabs and len(cm.tabs) > 1:
|
211
167
|
cm.browser.driver.switch_to.window(cm.tabs[0])
|
212
|
-
|
213
168
|
for tab in cm.tabs[1:]:
|
214
169
|
cm.browser.driver.switch_to.window(tab)
|
215
170
|
cm.browser.driver.close()
|
216
|
-
|
217
171
|
cm.tabs = [cm.tabs[0]]
|
218
172
|
cm.browser.driver.switch_to.window(cm.tabs[0])
|
219
|
-
print(f"{Fore.GREEN} Closed contest tabs successfully!
|
220
|
-
|
173
|
+
print(f"{Fore.GREEN} Closed contest tabs successfully! ✔")
|
221
174
|
dashboard_text = " Loading Dashboard now ..."
|
222
175
|
for char in dashboard_text:
|
223
176
|
sys.stdout.write(Fore.YELLOW + char)
|
224
177
|
sys.stdout.flush()
|
225
178
|
time.sleep(0.04)
|
226
|
-
|
227
179
|
sys.stdout.write('\n')
|
228
180
|
cm.goto_dashboard()
|
229
|
-
|
230
181
|
quest_list.pop(4)
|
231
182
|
quest_list.pop(3)
|
232
183
|
quest_list.insert(3, Choice(value="_contest_ini", name="Open Contest"))
|
233
|
-
|
234
|
-
# Reset problems list
|
235
184
|
cm.problems = []
|
236
|
-
|
237
|
-
# Reset contest ID and directory
|
238
185
|
cm.contest_id = None
|
239
186
|
cm.contest_dir = None
|
240
187
|
elif choice == '_track_problelm':
|
241
188
|
cm.show_tracker()
|
242
189
|
elif choice == '_contest_again':
|
243
|
-
# Close all problem tabs except the first one (contest page)
|
244
190
|
if cm.tabs and len(cm.tabs) > 1:
|
245
|
-
# Switch to first tab (contest page)
|
246
191
|
cm.browser.driver.switch_to.window(cm.tabs[0])
|
247
|
-
|
248
|
-
# Close all problem tabs
|
249
192
|
for tab in cm.tabs[1:]:
|
250
193
|
cm.browser.driver.switch_to.window(tab)
|
251
194
|
cm.browser.driver.close()
|
252
|
-
|
253
|
-
# Reset tabs list to only include the first tab
|
254
195
|
cm.tabs = [cm.tabs[0]]
|
255
196
|
cm.browser.driver.switch_to.window(cm.tabs[0])
|
256
|
-
print(f"{Fore.GREEN} Closed previous contest tabs successfully!
|
257
|
-
|
258
|
-
# Reset problems list
|
197
|
+
print(f"{Fore.GREEN} Closed previous contest tabs successfully! ✔")
|
259
198
|
cm.problems = []
|
260
|
-
|
261
|
-
# Reset contest ID and directory
|
262
199
|
cm.contest_id = None
|
263
200
|
cm.contest_dir = None
|
264
|
-
|
265
|
-
# Open new contest
|
266
201
|
cm.open_contest()
|
267
202
|
else: click.echo(f"{Fore.RED} Invalid choice!")
|
268
|
-
# --------------------------------------------------------
|
269
203
|
|
270
204
|
cli.add_command(interactive)
|
271
205
|
|
272
|
-
# ------ Main Script -------
|
273
206
|
if __name__ == "__main__":
|
274
207
|
interactive()
|
275
|
-
# --------------------------
|
@@ -0,0 +1,136 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: chefmate
|
3
|
+
Version: 1.0.2
|
4
|
+
Summary: Automation tool for CodeChef
|
5
|
+
Home-page: https://github.com/Anuj-72/ChefMate
|
6
|
+
Author: Anuj Kumar Sah
|
7
|
+
Author-email: Anuj Kumar Sah <anujsah282005@gmail.com>
|
8
|
+
License: MIT
|
9
|
+
Keywords: CodeChef,CLI,automation,competitive-programming
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: Topic :: Software Development :: Build Tools
|
15
|
+
Requires-Python: >=3.7
|
16
|
+
Description-Content-Type: text/markdown
|
17
|
+
License-File: LICENSE
|
18
|
+
Dynamic: author
|
19
|
+
Dynamic: home-page
|
20
|
+
Dynamic: license-file
|
21
|
+
Dynamic: requires-python
|
22
|
+
|
23
|
+
# ChefMate
|
24
|
+
|
25
|
+

|
26
|
+
|
27
|
+
ChefMate is a CLI tool for automating CodeChef tasks via an interactive menu-driven interface. It streamlines the workflow of logging in, opening contests, solving problems, running demo test cases, submitting solutions, and tracking problem statuses in real time.
|
28
|
+
|
29
|
+
## Features
|
30
|
+
|
31
|
+
- **Interactive Mode**: User-friendly, arrow-key-driven menu
|
32
|
+
- **Automated Login**: Seamlessly sign into CodeChef
|
33
|
+
- **Contest Management**: Open contests by ID directly in browser
|
34
|
+
- **Problem Solving Support**: Scaffold and open solution files locally
|
35
|
+
- **Demo Test Case Checking**: Run and verify sample tests before submission
|
36
|
+
- **Automated Submission**: Submit solutions without leaving the terminal
|
37
|
+
- **Problem Tracker**: View status (✅ done | 🟡 pending | ❌ wrong)
|
38
|
+
- **Configuration Management**: Easily reconfigure user preferences
|
39
|
+
- **Clear Terminal**: Wipe the screen for a fresh view
|
40
|
+
|
41
|
+
## Installation
|
42
|
+
|
43
|
+
You don’t need to clone any repository — just install via pip:
|
44
|
+
|
45
|
+
```bash
|
46
|
+
pip install chefmate
|
47
|
+
```
|
48
|
+
|
49
|
+
That’s it!
|
50
|
+
|
51
|
+
## Usage
|
52
|
+
|
53
|
+
Run ChefMate in interactive mode:
|
54
|
+
|
55
|
+
```bash
|
56
|
+
python main.py interactive
|
57
|
+
```
|
58
|
+
|
59
|
+
> If you have set up an entry point, you can also run:
|
60
|
+
|
61
|
+
```bash
|
62
|
+
chefmate interactive
|
63
|
+
```
|
64
|
+
|
65
|
+
## Initial Configuration
|
66
|
+
|
67
|
+
On first run, ChefMate will prompt you to configure key settings:
|
68
|
+
|
69
|
+
- **Username**: Your CodeChef username
|
70
|
+
- **Password**: Your CodeChef password (input hidden)
|
71
|
+
- **Default Solution Path**: Local directory or file path pattern for solutions
|
72
|
+
- **Chrome User Data Directory** (optional): Path to Chrome profile data
|
73
|
+
- **Chrome Profile** (optional): Profile name within Chrome
|
74
|
+
- **Preferred Language**: Language for submissions (C++, Python 3, Java, etc.)
|
75
|
+
- **Preferred Editor**: Editor to open solution files (default, VS Code, Sublime, etc.)
|
76
|
+
|
77
|
+

|
78
|
+
|
79
|
+
## Interactive Mode Options
|
80
|
+
|
81
|
+
When you launch interactive mode, you’ll see a menu:
|
82
|
+
|
83
|
+
```text
|
84
|
+
==================================================
|
85
|
+
ChefMate Interactive Mode
|
86
|
+
==================================================
|
87
|
+
```
|
88
|
+
|
89
|
+
Use arrow keys to navigate and Enter to select any of the following:
|
90
|
+
|
91
|
+
| Option | Description | Example GIF/Screenshot |
|
92
|
+
|--------------------------------|-------------------------------------------------------|---------------------------------------------|
|
93
|
+
| **Login** | Authenticate with CodeChef |  |
|
94
|
+
| **Open Contest** | Open a CodeChef contest by ID in your browser |  |
|
95
|
+
| **Solve Problems** | Scaffold and open solution templates locally |  |
|
96
|
+
| **Check Demo test cases** | Run sample tests on your solution |  |
|
97
|
+
| **Submit Solution** | Submit your solution to CodeChef |  |
|
98
|
+
| **Problem Tracker** | View current status of each problem |  |
|
99
|
+
| **Re-configure ChefMate** | Update any configuration options | |
|
100
|
+
| **Clear Terminal** | Clear your terminal screen | |
|
101
|
+
| **Logout** | Sign out of your CodeChef session | |
|
102
|
+
| **Exit / Logout and Exit** | Quit interactive mode (and logout if selected) | |
|
103
|
+
|
104
|
+
## Visual Walkthrough
|
105
|
+
|
106
|
+
### Start ChefMate
|
107
|
+
|
108
|
+

|
109
|
+
|
110
|
+
### Logging In
|
111
|
+
|
112
|
+

|
113
|
+
|
114
|
+
### Opening a Contest
|
115
|
+
|
116
|
+

|
117
|
+
|
118
|
+
### Solving Problems
|
119
|
+
|
120
|
+

|
121
|
+
|
122
|
+
### Checking Demo Test Cases
|
123
|
+
|
124
|
+

|
125
|
+
|
126
|
+
### Submitting a Solution
|
127
|
+
|
128
|
+

|
129
|
+
|
130
|
+
### Problem Tracker
|
131
|
+
|
132
|
+

|
133
|
+
|
134
|
+
## License
|
135
|
+
|
136
|
+
This project is released under the MIT License. See [LICENSE](LICENSE) for details.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
chefmate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
chefmate/browser_manager.py,sha256=V1khfGVj8aeOmuQmVZLHs5H56vZRvtI97-nNe6pTK6E,2866
|
3
|
+
chefmate/chefmate_core.py,sha256=WlVAb20animW0RsgeDxep2TNslWRTSEylHnlrZFMClg,72178
|
4
|
+
chefmate/cli.py,sha256=qHVlNukl7vjEVXzdCJRT38XtbhaXCmcRUe4D3DAqBsE,7706
|
5
|
+
chefmate/config.py,sha256=itkFruFKMX7eday8UnAYOfnVMthivn20tiTlG87czyA,2220
|
6
|
+
chefmate-1.0.2.dist-info/licenses/LICENSE,sha256=dBIqGYz89ZL5FXsfdbOvpsPupYvcJfnkimw2vD7jZ94,1090
|
7
|
+
chefmate-1.0.2.dist-info/METADATA,sha256=76xTW6WEYRQDV3WEGGd_5314I7KUm4CdQtmlgI2wx-E,5240
|
8
|
+
chefmate-1.0.2.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
9
|
+
chefmate-1.0.2.dist-info/top_level.txt,sha256=jOlvGtNNEYlsbwLBD2lpAmZBSo1wq2dydfAM1lP_Ago,9
|
10
|
+
chefmate-1.0.2.dist-info/RECORD,,
|
@@ -1,313 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: chefmate
|
3
|
-
Version: 1.0.0
|
4
|
-
Summary: A CodeChef automation CLI tool
|
5
|
-
Home-page: https://github.com/Anuj-72/ChefMate
|
6
|
-
Author: Anuj Kumar Sah
|
7
|
-
Author-email: anujsah282005@gmail.com
|
8
|
-
Classifier: Programming Language :: Python :: 3
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Requires-Python: >=3.7
|
12
|
-
Description-Content-Type: text/markdown
|
13
|
-
License-File: LICENSE
|
14
|
-
Requires-Dist: selenium
|
15
|
-
Requires-Dist: webdriver-manager
|
16
|
-
Requires-Dist: click
|
17
|
-
Requires-Dist: colorama
|
18
|
-
Requires-Dist: inquirer
|
19
|
-
Requires-Dist: InquirerPy
|
20
|
-
Requires-Dist: rich
|
21
|
-
Requires-Dist: tabulate
|
22
|
-
Requires-Dist: pandas
|
23
|
-
Dynamic: author
|
24
|
-
Dynamic: author-email
|
25
|
-
Dynamic: classifier
|
26
|
-
Dynamic: description
|
27
|
-
Dynamic: description-content-type
|
28
|
-
Dynamic: home-page
|
29
|
-
Dynamic: license-file
|
30
|
-
Dynamic: requires-dist
|
31
|
-
Dynamic: requires-python
|
32
|
-
Dynamic: summary
|
33
|
-
|
34
|
-
# ChefMate
|
35
|
-
|
36
|
-
ChefMate is an automation tool designed to simplify and streamline your CodeChef contest participation. It leverages browser automation (using Selenium and ChromeDriver) along with a versatile command-line interface (CLI) to handle tasks such as logging in, contest management, problem scraping, solution submissions, and demo test case validations. ChefMate also helps set up directories and template files so that you can focus on solving problems quickly and efficiently.
|
37
|
-
|
38
|
-
---
|
39
|
-
|
40
|
-
## Table of Contents
|
41
|
-
|
42
|
-
- [Introduction](#introduction)
|
43
|
-
- [Features](#features)
|
44
|
-
- [Architecture and Code Structure](#architecture-and-code-structure)
|
45
|
-
- [Browser Manager (`browser_manager.py`)](#browser-manager)
|
46
|
-
- [Core Functionality (`chefmate_core.py`)](#core-functionality)
|
47
|
-
- [Command Line Interface (`cli.py`)](#command-line-interface)
|
48
|
-
- [Configuration Management (`config.py`)](#configuration-management)
|
49
|
-
- [Installation](#installation)
|
50
|
-
- [Usage](#usage)
|
51
|
-
- [Setup](#setup)
|
52
|
-
- [Login and Logout](#login-and-logout)
|
53
|
-
- [Contest and Problem Management](#contest-and-problem-management)
|
54
|
-
- [Testing and Submissions](#testing-and-submissions)
|
55
|
-
- [Interactive Mode](#interactive-mode)
|
56
|
-
- [Dependencies](#dependencies)
|
57
|
-
- [Troubleshooting](#troubleshooting)
|
58
|
-
- [Future Improvements](#future-improvements)
|
59
|
-
- [License](#license)
|
60
|
-
|
61
|
-
---
|
62
|
-
|
63
|
-
## Introduction
|
64
|
-
|
65
|
-
ChefMate is built to help competitive programmers who participate in CodeChef contests. By automating several routine tasks (such as logging in, opening contest pages, fetching problems, and managing solution submissions), ChefMate removes friction from the contest environment. With its modular design, the tool offers easy configurability and a friendly CLI interface, making the CodeChef contest experience smoother and more productive.
|
66
|
-
|
67
|
-
---
|
68
|
-
|
69
|
-
## Features
|
70
|
-
|
71
|
-
- **Browser Management:**
|
72
|
-
- Detects and prevents conflicts with existing Chrome sessions.
|
73
|
-
- Initializes a ChromeDriver with custom profiles and directories.
|
74
|
-
- Configurable options to bypass common Chrome logging and automation detections.
|
75
|
-
|
76
|
-
- **Automated Contest Interaction:**
|
77
|
-
- Logs into CodeChef and navigates to the contest dashboard.
|
78
|
-
- Opens contest pages and scrapes problem links and details.
|
79
|
-
- Provides an interactive problem tracker to monitor submission statuses.
|
80
|
-
|
81
|
-
- **Solution Management:**
|
82
|
-
- Creates and manages solution directories for contests.
|
83
|
-
- Generates template solution files in your preferred programming language.
|
84
|
-
- Facilitates opening solution files in your favorite code editor.
|
85
|
-
|
86
|
-
- **Submission and Testing:**
|
87
|
-
- Automates the process of selecting the language from the CodeChef UI.
|
88
|
-
- Loads solutions from files and submits them via the browser.
|
89
|
-
- Runs sample test cases to verify outputs before actual submission.
|
90
|
-
- Displays detailed verdict tables and highlights errors.
|
91
|
-
|
92
|
-
- **Command-Line Interface (CLI):**
|
93
|
-
- Offers a range of commands (setup, login, logout, contest, submit, check, interactive mode).
|
94
|
-
- Provides an intuitive interactive mode with a clear menu-driven experience.
|
95
|
-
- Uses libraries such as Click and InquirerPy for enhanced user interaction.
|
96
|
-
|
97
|
-
- **Configuration Management:**
|
98
|
-
- Manages user settings (username, password, solution paths, preferred language, Chrome profile, editor preferences) through a JSON configuration file stored in the user's home directory.
|
99
|
-
- Ensures persistence and easy updates of user configuration.
|
100
|
-
|
101
|
-
---
|
102
|
-
|
103
|
-
## Architecture and Code Structure
|
104
|
-
|
105
|
-
ChefMate’s code is organized into four main modules:
|
106
|
-
|
107
|
-
### Browser Manager
|
108
|
-
|
109
|
-
**File:** `browser_manager.py`
|
110
|
-
|
111
|
-
- **Purpose:**
|
112
|
-
Manages the Chrome browser session using Selenium WebDriver.
|
113
|
-
- **Key Functions:**
|
114
|
-
- **Initialization:** Sets up the ChromeDriver with options such as user-data-dir and profile-directory for isolated sessions.
|
115
|
-
- **Conflict Detection:** Checks if a Chrome instance is already running and prompts the user accordingly.
|
116
|
-
- **Session Management:** Provides methods to initialize, operate, and close the browser session.
|
117
|
-
- **Highlights:**
|
118
|
-
Uses `webdriver_manager` to ensure the correct ChromeDriver is installed and employs custom options to reduce unwanted automation flags.
|
119
|
-
|
120
|
-
### Core Functionality
|
121
|
-
|
122
|
-
**File:** `chefmate_core.py`
|
123
|
-
|
124
|
-
- **Purpose:**
|
125
|
-
Acts as the primary interface to ChefMate functionality. It integrates browser automation with the CodeChef website.
|
126
|
-
- **Components:**
|
127
|
-
- **Login/Logout:**
|
128
|
-
Automates the CodeChef login procedure including form filling, waiting for dashboard elements, and handling potential errors.
|
129
|
-
- **Contest Handling:**
|
130
|
-
Opens a contest page, scrapes all available problem links, and dynamically creates a problem tracker.
|
131
|
-
- **Solution Handling:**
|
132
|
-
Searches for or prompts for the correct solution file. Generates solution templates based on the preferred programming language.
|
133
|
-
- **Submission and Testing:**
|
134
|
-
Provides mechanisms to load code into the CodeChef code editor, submit solutions, and analyze output results by parsing verdict tables.
|
135
|
-
- **Interactive Utilities:**
|
136
|
-
Functions like typewriter text effects, dashboard redirection, and dynamic problem tracking to enhance the user experience.
|
137
|
-
|
138
|
-
### Command Line Interface
|
139
|
-
|
140
|
-
**File:** `cli.py`
|
141
|
-
|
142
|
-
- **Purpose:**
|
143
|
-
Provides a user-friendly CLI to interact with ChefMate.
|
144
|
-
- **Features:**
|
145
|
-
- **CLI Commands:**
|
146
|
-
Implements commands using Click. Commands include `setup`, `login`, `logout`, `check`, `submit`, `contest`, and `interactive`.
|
147
|
-
- **Interactive Mode:**
|
148
|
-
An extended menu-driven interface built using InquirerPy that lets users select among different operational modes such as logging in, solving problems, or reconfiguring settings.
|
149
|
-
- **UI Enhancements:**
|
150
|
-
Uses the Rich library for improved terminal output, such as styled tables and animated loaders.
|
151
|
-
|
152
|
-
### Configuration Management
|
153
|
-
|
154
|
-
**File:** `config.py`
|
155
|
-
|
156
|
-
- **Purpose:**
|
157
|
-
Handles persistent configurations for ChefMate by reading from and writing to a JSON file.
|
158
|
-
- **Details:**
|
159
|
-
- **Default Configuration:**
|
160
|
-
Automatically creates a configuration file in the user's home directory (`~/.chefmate/config.json`) if it does not exist.
|
161
|
-
- **Dynamic Updates:**
|
162
|
-
Offers methods to get, set, and update various settings including username, password, preferred language, solution file paths, and Chrome user data directories.
|
163
|
-
- **Robustness:**
|
164
|
-
Includes error handling to recreate a default configuration if the current configuration file is corrupted.
|
165
|
-
|
166
|
-
---
|
167
|
-
|
168
|
-
## Installation
|
169
|
-
|
170
|
-
1. **Clone the Repository:**
|
171
|
-
|
172
|
-
```bash
|
173
|
-
git clone https://github.com/yourusername/chefmate.git
|
174
|
-
cd chefmate
|
175
|
-
```
|
176
|
-
|
177
|
-
2. **Set Up a Python Virtual Environment (Optional but Recommended):**
|
178
|
-
|
179
|
-
```bash
|
180
|
-
python -m venv venv
|
181
|
-
source venv/bin/activate # Linux/Mac
|
182
|
-
venv\Scripts\activate # Windows
|
183
|
-
```
|
184
|
-
|
185
|
-
3. **Install Dependencies:**
|
186
|
-
|
187
|
-
ChefMate relies on several Python packages. Install them using:
|
188
|
-
|
189
|
-
```bash
|
190
|
-
pip install -r requirements.txt
|
191
|
-
```
|
192
|
-
|
193
|
-
> **Note:** Dependencies include libraries like Selenium, webdriver-manager, Click, InquirerPy, Rich, and Colorama. Make sure your environment meets the prerequisites for Selenium and ChromeDriver.
|
194
|
-
|
195
|
-
---
|
196
|
-
|
197
|
-
## Usage
|
198
|
-
|
199
|
-
### Setup
|
200
|
-
|
201
|
-
Run the setup command to configure ChefMate with your CodeChef credentials, preferred language, editor, and solution file path:
|
202
|
-
|
203
|
-
```bash
|
204
|
-
python cli.py setup
|
205
|
-
```
|
206
|
-
|
207
|
-
### Login and Logout
|
208
|
-
|
209
|
-
To login to CodeChef, use:
|
210
|
-
|
211
|
-
```bash
|
212
|
-
python cli.py login
|
213
|
-
```
|
214
|
-
|
215
|
-
And to logout:
|
216
|
-
|
217
|
-
```bash
|
218
|
-
python cli.py logout
|
219
|
-
```
|
220
|
-
|
221
|
-
### Contest and Problem Management
|
222
|
-
|
223
|
-
- **Opening a Contest:**
|
224
|
-
Launch a contest by providing the contest ID and your division using:
|
225
|
-
|
226
|
-
```bash
|
227
|
-
python cli.py contest
|
228
|
-
```
|
229
|
-
|
230
|
-
- **Automatic Problem Scraping:**
|
231
|
-
ChefMate will automatically identify and open all the contest problem tabs and display a problem tracker.
|
232
|
-
|
233
|
-
### Testing and Submissions
|
234
|
-
|
235
|
-
- **Check Demo Test Cases:**
|
236
|
-
Validate your solution using sample test cases:
|
237
|
-
|
238
|
-
```bash
|
239
|
-
python cli.py check --problem 1
|
240
|
-
```
|
241
|
-
|
242
|
-
- **Submitting a Solution:**
|
243
|
-
To submit your solution for a given problem:
|
244
|
-
|
245
|
-
```bash
|
246
|
-
python cli.py submit --problem 1
|
247
|
-
```
|
248
|
-
|
249
|
-
- **Solving Problems (Template Generation & File Management):**
|
250
|
-
ChefMate automatically generates solution template files in a designated contest directory for you to edit. It opens the selected file in your preferred text editor.
|
251
|
-
|
252
|
-
### Interactive Mode
|
253
|
-
|
254
|
-
ChefMate also offers an interactive mode that provides a dynamic menu for operations. Launch it with:
|
255
|
-
|
256
|
-
```bash
|
257
|
-
python cli.py interactive
|
258
|
-
```
|
259
|
-
|
260
|
-
In this mode, you can choose among several actions such as login, logout, opening contests, checking demo cases, submitting solutions, and reconfiguring settings.
|
261
|
-
|
262
|
-
---
|
263
|
-
|
264
|
-
## Dependencies
|
265
|
-
|
266
|
-
- **Selenium:** For browser automation and web interactions.
|
267
|
-
- **webdriver-manager:** To handle ChromeDriver installation and updates.
|
268
|
-
- **Click & InquirerPy:** For building the CLI and interactive prompts.
|
269
|
-
- **Rich & Colorama:** For styled terminal outputs and color support.
|
270
|
-
- **Pandas & Tabulate:** For generating and displaying submission verdict tables.
|
271
|
-
|
272
|
-
Make sure you have the latest version of Google Chrome installed to ensure compatibility with ChromeDriver.
|
273
|
-
|
274
|
-
---
|
275
|
-
|
276
|
-
## Troubleshooting
|
277
|
-
|
278
|
-
- **Chrome Profile in Use:**
|
279
|
-
If you receive an error indicating that the Chrome profile is already being used, ensure that all Chrome windows are closed before starting ChefMate.
|
280
|
-
|
281
|
-
- **Solution File Issues:**
|
282
|
-
If ChefMate cannot locate your solution file, double-check the path in the configuration (or re-run the `setup` command).
|
283
|
-
|
284
|
-
- **Configuration Errors:**
|
285
|
-
If the configuration file is corrupted, ChefMate will automatically generate a new default configuration in `~/.chefmate/config.json`.
|
286
|
-
|
287
|
-
- **Network/Slow Load Problems:**
|
288
|
-
For slow networks, ChefMate includes retries during login and test case checks. If problems persist, try restarting your browser or re-running the command.
|
289
|
-
|
290
|
-
---
|
291
|
-
|
292
|
-
## Future Improvements
|
293
|
-
|
294
|
-
- **Enhanced Error Reporting:**
|
295
|
-
Additional debugging information for failures during submissions and test case validations.
|
296
|
-
- **Multi-Language Support:**
|
297
|
-
Extending support for more languages and custom code editor integration.
|
298
|
-
- **GUI Implementation:**
|
299
|
-
A graphical user interface for those who prefer not to use the CLI.
|
300
|
-
- **Extended Contest Support:**
|
301
|
-
Integration with more competitive programming sites.
|
302
|
-
|
303
|
-
---
|
304
|
-
|
305
|
-
## License
|
306
|
-
|
307
|
-
ChefMate is open source software released under the [MIT License](LICENSE).
|
308
|
-
|
309
|
-
---
|
310
|
-
|
311
|
-
ChefMate is designed with flexibility and practicality in mind to reduce the repetitive tasks of contest participation, leaving you free to concentrate on solving problems and improving your competitive programming skills.
|
312
|
-
|
313
|
-
Happy Coding!
|
chefmate-1.0.0.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
chefmate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
chefmate/browser_manager.py,sha256=V1khfGVj8aeOmuQmVZLHs5H56vZRvtI97-nNe6pTK6E,2866
|
3
|
-
chefmate/chefmate_core.py,sha256=F66-7gJJtn0_a6nM3HWLe58-3ExcAA82HK0ujmzrmEU,69631
|
4
|
-
chefmate/cli.py,sha256=5GtohPcnSY8ygqLqZyMwZQioWfvMlF9KHTvYN15CB-4,9323
|
5
|
-
chefmate/config.py,sha256=itkFruFKMX7eday8UnAYOfnVMthivn20tiTlG87czyA,2220
|
6
|
-
chefmate-1.0.0.dist-info/licenses/LICENSE,sha256=gIUzdNdf-vbONssDRz-fj0uTywroTk9_ZBedvpj57XU,1094
|
7
|
-
chefmate-1.0.0.dist-info/METADATA,sha256=0rjISJSFc-cUYZqDtbWJdvmf1I8_0Rca49Vbe7to_B4,11858
|
8
|
-
chefmate-1.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
9
|
-
chefmate-1.0.0.dist-info/entry_points.txt,sha256=JajQonz6c_uzwcHALUk6VA_zxMhDPsdHC-Z7lJedyyA,46
|
10
|
-
chefmate-1.0.0.dist-info/top_level.txt,sha256=jOlvGtNNEYlsbwLBD2lpAmZBSo1wq2dydfAM1lP_Ago,9
|
11
|
-
chefmate-1.0.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|