Skip to content

Commit a1aa93d

Browse files
committed
Create the hlper script yoo
1 parent ad2bfc8 commit a1aa93d

File tree

6 files changed

+322
-0
lines changed

6 files changed

+322
-0
lines changed

requirements.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
markdownify
2+
beautifulsoup4
3+
pytermgui
4+
lxml
5+
selenium
6+
requests

solve.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import os # For getting the current working directory and making directories
2+
import sys # For getting command line arguments
3+
from enum import Enum # For the enum class
4+
from selenium import webdriver # For getting the HTML of the problem
5+
import requests # For downloading images
6+
from bs4 import BeautifulSoup # For parsing HTML
7+
import markdownify as md # For converting HTML to Markdown
8+
sys.path.append("utils") # Add the utils directory to the path
9+
import pytermgui_tui as tui # Import the TUI
10+
from data import Data # Import the Data class
11+
from projcets_helpers import * # Import the projects creation methods
12+
13+
BASE_URL = "https://leetcode.com/problems/" # The base URL of the problem
14+
15+
BASE_URL = BASE_URL + "merge-two-sorted-lists" # The URL of the problem for testing
16+
17+
# Stup the tui
18+
tui.setup()
19+
20+
# Check if user provided a URL as an argument
21+
if len(sys.argv) < 2:
22+
# If not, ask for one
23+
problem_url = tui.get_the_url(BASE_URL)
24+
else:
25+
# If so, use it
26+
problem_url = sys.argv[1]
27+
# Check if user provided a problem title instead of a URL, if so, add the base URL
28+
if not problem_url.startswith(BASE_URL) and not problem_url.startswith("http"):
29+
problem_url = BASE_URL + problem_url
30+
31+
# Check if the URL is valid
32+
if not problem_url.startswith(BASE_URL):
33+
print("Invalid URL, please enter a valid URL(LeeCode problem URL)")
34+
exit(1)
35+
36+
# Setup the driver (firefox)
37+
driver = webdriver.Firefox()
38+
39+
driver.get(problem_url) # Open the URL in the browser
40+
41+
soup = BeautifulSoup(driver.page_source, "lxml") # Parse the HTML
42+
43+
driver.quit() # Close the driver
44+
45+
# Get the main div (the problem details are in this div)
46+
main_div = soup.find("div", {"class": "ssg__qd-splitter-primary-w"}).find("div", {"class": "ssg__qd-splitter-primary-h"})
47+
48+
# Get the title of the problem
49+
title = soup.title.string.lower().replace(" - leetcode", "").replace(" ", "_")
50+
51+
level = main_div.find("div", {"class": "mt-3 flex space-x-4"}).find("div", {"class": "py-1"}).text.lower()
52+
53+
# Check if the level directory exists, if not, create it
54+
if not os.path.exists(level):
55+
os.mkdir(level)
56+
57+
# Check if the problem directory exists, if not, create it
58+
problem_path = os.path.join(level, title)
59+
if not os.path.exists(problem_path):
60+
os.mkdir(problem_path)
61+
62+
# Get the description of the problem
63+
discription = main_div.find("div", {"class": "_1l1MA"})
64+
65+
# Show the tui for confirm the data and choose the language to solve the problem
66+
data = tui.confirm_data(Data(title, level, problem_path))
67+
68+
# Download the images if there are any
69+
for img in discription.find_all("img"):
70+
src = img["src"]
71+
req = requests.get(src)
72+
if req.status_code == 200:
73+
img_name = src.split("/")[-1]
74+
img_path = os.path.join(data.problem_path, "images", img_name)
75+
if not os.path.exists(img_path):
76+
if not os.path.exists(os.path.dirname(img_path)):
77+
os.makedirs(os.path.dirname(img_path))
78+
with open(img_path, "wb") as f:
79+
f.write(req.content)
80+
img["src"] = img_path.replace(data.problem_path, ".")
81+
82+
# Convert the discription to Markdown
83+
discription = md.markdownify(str(discription), heading_style="ATX")
84+
85+
# Add the title to the discription
86+
discription = "# " + data.title.capitalize().replace("_", " ") + "\n" + discription
87+
88+
# Add the problem URL to the discription
89+
discription = discription + "\n- [Problem URL](" + problem_url + ")"
90+
91+
# Write the discription to the README.md file, if it doesn't exist
92+
if not os.path.exists(os.path.join(data.problem_path, "README.md")):
93+
with open(os.path.join(data.problem_path, "README.md"), "w") as f:
94+
f.write(discription)
95+
96+
# Create the NOTE.md file, if it doesn't exist
97+
if not os.path.exists(os.path.join(data.problem_path, "NOTE.md")):
98+
with open(os.path.join(data.problem_path, "NOTE.md"), "w") as f:
99+
f.write("## There is no note for this problem yet ¯⁠⁠⁠\(⁠ツ⁠)⁠⁠/⁠¯")
100+
101+
# Create the solution project for each language
102+
for lang in data.solve_with:
103+
lang_path = os.path.join(data.problem_path, lang)
104+
if not os.path.exists(lang_path):
105+
os.mkdir(lang_path)
106+
match lang:
107+
case "python" | "py":
108+
create_python_project(lang_path)
109+
case "java":
110+
create_java_project(lang_path)
111+
case "c++" | "cpp":
112+
create_cpp_project(lang_path)
113+
case "c":
114+
create_c_project(lang_path)
115+
case "rust":
116+
create_rust_project(lang_path)
117+
case "go":
118+
create_go_project(lang_path)
119+
case _: # If other language, do nothing
120+
pass
121+

utils/data.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class Data:
2+
def __init__(self, title: str, level: str, problem_path: str, solve_with: list = None):
3+
self.title = title
4+
self.level = level
5+
self.problem_path = problem_path
6+
self.solve_with = solve_with
7+
8+
def __str__(self) -> str:
9+
return f"Title: {self.title} | Level: {self.level} | Path: {self.problem_path} | Solve with: {self.solve_with}"

utils/projcets_helpers.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import subprocess
2+
from os import sep, mkdir, chdir
3+
4+
def run(command: str, path: str = None) -> bool:
5+
""" Run a command utility"""
6+
# Go to the directory of the project if there is one
7+
if path:
8+
chdir(path)
9+
result = subprocess.run(command.split(' '), capture_output=True)
10+
print(result.stdout.decode('utf-8'))
11+
12+
# Go back to the root directory of the project
13+
if path:
14+
chdir(sep.join(__file__.split(sep)[:-2]))
15+
16+
# Print the error if there is one
17+
if result.stderr:
18+
print(f"There was an error while running the command: {command}")
19+
print(result.stderr.decode('utf-8'))
20+
21+
return result.returncode == 0
22+
23+
def create_rust_project(path: str) -> None:
24+
""" Create a rust project """
25+
run(f"cargo new --vcs none --lib {path}")
26+
27+
def create_c_project(path: str) -> None:
28+
""" Create a c project """
29+
run(f"touch {path}/main.c")
30+
run(f"touch {path}/Makefile")
31+
run(f"touch {path}/.gitignore")
32+
33+
def create_python_project(path: str) -> None:
34+
""" Create a python project """
35+
run(f"touch {path}/main.py")
36+
with open(f"{path}/test.py", "w") as f:
37+
f.write("import unittest\n")
38+
f.write("from main import *\n\n")
39+
f.write("class Test(unittest.TestCase):\n")
40+
f.write("\t def test(self):\n")
41+
f.write("\t\t self.assertEqual(True, True)\n\n")
42+
f.write("if __name__ == '__main__':\n")
43+
f.write("\t unittest.main()\n")
44+
45+
def create_java_project(path: str) -> None:
46+
if not run(f"mvn archetype:generate \
47+
-DarchetypeGroupId=org.apache.maven.archetypes \
48+
-DarchetypeArtifactId=maven-archetype-quickstart \
49+
-DarchetypeVersion=1.4 \
50+
-DgroupId=com.anas.leetcode.{path.split(sep)[-2]} \
51+
-DartifactId={path.split(sep)[-2]} \
52+
-Dversion=1.0-SNAPSHOT"):
53+
print("Failed to create java project")
54+
return
55+
56+
def create_cpp_project(path: str) -> None:
57+
with open(f"{path}/main.cpp", "w") as f:
58+
f.write("#include <iostream>\n\n")
59+
f.write("using namespace std;\n\n")
60+
61+
with open(f"{path}/test.cpp", "w") as f:
62+
f.write("#include <catch2/catch.hpp>\n")
63+
f.write("#include \"main.cpp\"\n\n")
64+
65+
with open(f"{path}/Makefile", "w") as f:
66+
f.write("all: main.cpp test.cpp\n")
67+
f.write("\t g++ -o main main.cpp\n")
68+
f.write("\t g++ -o test test.cpp -I /usr/local/include -L /usr/local/lib -lcatch2\n")
69+
f.write("\t ./test\n")
70+
71+
def create_go_project(path: str) -> None:
72+
if run(f"go mod init letcode/{path.split(sep)[-2]}", path):
73+
mkdir(f"{path}/src")
74+
with open(f"{path}/src/main.go", "w") as f:
75+
f.write("package main\n\n")
76+
f.write("import \"fmt\"\n\n")
77+
f.write("func main() {\n")
78+
f.write("\t fmt.Println(\"Hello, World!\")\n")
79+
f.write("}\n")
80+
81+
with open(f"{path}/src/_test.go", "w") as f:
82+
f.write("package main\n\n")
83+
f.write("import \"testing\"\n\n")
84+
f.write("func TestHello(t *testing.T) {\n")
85+
f.write("\t fmt.Println(\"Hello, World!\")\n")
86+
f.write("}\n")
87+

utils/pytermgui_tui.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import pytermgui as ptg
2+
from data import Data
3+
4+
CONFIG = """
5+
config:
6+
InputField:
7+
styles:
8+
prompt: dim italic
9+
cursor: '@72'
10+
Label:
11+
styles:
12+
value: dim bold
13+
14+
Window:
15+
styles:
16+
border: '60'
17+
corner: '60'
18+
19+
Container:
20+
styles:
21+
border: '96'
22+
corner: '96'
23+
"""
24+
25+
problem_url = "" # The URL of the problem
26+
27+
def setup() -> None:
28+
"""
29+
Stup the pytermgui
30+
load the config styles
31+
"""
32+
with ptg.YamlLoader() as loader:
33+
loader.load(CONFIG)
34+
35+
def submit_url(manager: ptg.WindowManager, window: ptg.Window) -> None:
36+
"""Submit the URL of the problem"""
37+
global problem_url
38+
for widget in window:
39+
if isinstance(widget, ptg.InputField):
40+
problem_url = widget.value
41+
break
42+
manager.stop()
43+
44+
def get_the_url(base_url: str) -> str:
45+
""" Show the tui using the pytermgui for enter the URL of the problem """
46+
with ptg.WindowManager() as wm:
47+
wm.layout.add_slot("Body")
48+
window = (
49+
ptg.Window(
50+
"",
51+
ptg.InputField(base_url, prompt="URL: "),
52+
"",
53+
["Submit", lambda *_: submit_url(wm, window)],
54+
width=60,
55+
box="DOUBLE",
56+
)
57+
.set_title("[green bold]Enter the URL of the problem")
58+
.center()
59+
)
60+
wm.add(window)
61+
wm.run()
62+
return problem_url
63+
64+
def confirm_data(data: Data) -> Data:
65+
""" Show the tui using the pytermgui for confirm the data and choose the language to solve the problem """
66+
with ptg.WindowManager() as wm:
67+
wm.layout.add_slot("Body")
68+
window = (
69+
ptg.Window(
70+
"",
71+
ptg.InputField(data.title, prompt="Title: "),
72+
ptg.InputField(data.level, prompt="Level: "),
73+
ptg.InputField(data.problem_path, prompt="Base path:"),
74+
"",
75+
["Confirm", lambda *_: wm.stop()],
76+
width=60,
77+
box="DOUBLE",
78+
)
79+
.set_title("[green bold]Confirm the data")
80+
.center()
81+
)
82+
wm.add(window)
83+
wm.run()
84+
85+
data.solve_with = ["python", "java", "c++", "c", "go"]
86+
return data

utils/tui.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import zope.interface
2+
from data import Data
3+
4+
class ITextUserInterface(zope.interface.Interface):
5+
def setup(self) -> None:
6+
"""Stup the tui"""
7+
pass
8+
def get_the_url(self, base_url: str) -> str:
9+
"""Show the tui for enter the URL of the problem"""
10+
pass
11+
def confirm_data(self, data: Data) -> Data:
12+
"""Show the tui for confirm the data and choose the language to solve the problem"""
13+
pass

0 commit comments

Comments
 (0)