Skip to content

Commit 175f893

Browse files
authored
Jinja and Code Solutions (#1)
* Add jinja and code solutions * Fix formatters
1 parent bf60f76 commit 175f893

14 files changed

+650
-145
lines changed

.DS_Store

0 Bytes
Binary file not shown.

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
## 1.3.5
3+
- Add Jinja templates in favor over standard library for string formatting
4+
- Add problems pulled from Neetcode solutions GitHub
25

36
## 1.3.4
47
- Add Neetcode 250

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
recursive-include leetcode_study_tool/templates *

Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ all:
1010

1111
format:
1212
python -m ruff format ./leetcode_study_tool
13+
python -m ruff check ./leetcode_study_tool --fix
1314

1415
format-check:
15-
python -m ruff check ./leetcode_study_tool
1616
python -m ruff format ./leetcode_study_tool --check
17+
python -m ruff check ./leetcode_study_tool
1718

1819
test:
1920
pytest tests/ --cov --cov-fail-under=85

README.md

+89-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ $ pip install leetcode-study-tool
2020
## 💻 Usage
2121
```shell
2222
usage: leetcode-study-tool [-h] (--url URL | --file FILE | --preset {blind_75,grind_75,grind_169,neetcode_150,neetcode_250,neetcode_all}) [--format {anki,excel}]
23-
[--csrf CSRF] [--output OUTPUT] [--language LANGUAGE]
23+
[--template TEMPLATE] [--csrf CSRF] [--output OUTPUT] [--language LANGUAGE] [--include-code]
2424

2525
Generates problems from LeetCode questions in a desired format.
2626

@@ -32,11 +32,14 @@ options:
3232
The preset to use to generate problem(s) for. (default: None)
3333
--format {anki,excel}, -F {anki,excel}
3434
The format to save the Leetcode problem(s) in. (default: anki)
35+
--template TEMPLATE, -t TEMPLATE
36+
Path to a custom Jinja template file for rendering problems. (default: None)
3537
--csrf CSRF, -c CSRF The CSRF token to use for LeetCode authentication. (default: None)
3638
--output OUTPUT, -o OUTPUT
3739
The output file to write the problem(s) to. (default: output.txt)
3840
--language LANGUAGE, -l LANGUAGE
3941
The language to generate problem(s) for. (default: None)
42+
--include-code, -ic Include solution code from NeetCode GitHub repository using the specified language. (default: False)
4043
```
4144

4245
## 💡 Example
@@ -52,10 +55,22 @@ which will generate the file `output.txt`. We can then open Anki to import these
5255

5356
![anki demo](static/anki-demo.gif)
5457

58+
### Including Solution Code
59+
60+
You can include solution code from the NeetCode GitHub repository by using the `--include-code` flag along with specifying a programming language:
61+
62+
```shell
63+
$ leetcode-study-tool -p grind_75 --language python --include-code
64+
```
65+
66+
This will fetch solution code in the specified language (when available) and include it in your Anki cards or Excel output.
67+
68+
Supported languages include: c, cpp, csharp, dart, go, java, javascript, kotlin, python, ruby, rust, scala, swift, and typescript.
69+
5570
## 📒 Anki
5671
When generating an Anki output, the resulting "cards" are saved as a `.txt` file. These cards include three fields:
5772
1. The front of the study card, containing the question ID, Title, URL, and problem description
58-
2. The publicly available solutions (and NeetCode solution, if available)
73+
2. The publicly available solutions (and NeetCode solution or code, if available)
5974
3. The tags associated with the problem (i.e., if the problem involves a hash map, arrays, etc...)
6075

6176
## 📊 Excel
@@ -69,12 +84,83 @@ When generating an Excel output, the resulting questions are saved in an `.xlsx`
6984
7. Solution links for the problem (if they are reachable)
7085
8. Companies that have asked this question recently in interviews (if they are reachable)
7186

87+
## Custom Templates
88+
89+
LeetCode Study Tool supports custom Jinja2 templates for generating Anki cards or other outputs. You can specify your own template file using the `--template` flag:
90+
91+
```bash
92+
leetcode-study-tool --url "https://leetcode.com/problems/two-sum/" --template "path/to/my_template.jinja"
93+
```
94+
95+
### Template Variables
96+
97+
When creating your custom template, the following variables are available:
98+
99+
| Variable | Description |
100+
|----------|-------------|
101+
| `url` | The URL to the LeetCode problem |
102+
| `slug` | The problem slug |
103+
| `data` | Object containing all problem data |
104+
| `data.id` | Problem ID |
105+
| `data.title` | Problem title |
106+
| `data.content` | Problem description (HTML) |
107+
| `data.difficulty` | Problem difficulty (Easy, Medium, Hard) |
108+
| `data.tags` | List of topic tags for the problem |
109+
| `data.companies` | List of companies that ask this problem |
110+
| `data.solutions` | List of available solutions on LeetCode |
111+
| `data.neetcode_solution` | NeetCode solution code (if --include-code is used) |
112+
| `data.language` | Language of the solution code |
113+
| `neetcode` | NeetCode video information (when available) |
114+
115+
### Solution Code Example
116+
117+
Here's an example template that highlights the solution code:
118+
119+
```jinja
120+
<h1>{{ data.id }}. {{ data.title }} ({{ data.difficulty }})</h1>
121+
122+
<div class="content">
123+
{{ data.content }}
124+
</div>
125+
126+
<div class="tags">
127+
{% for tag in data.tags %}
128+
<span class="tag">{{ tag.name }}</span>
129+
{% endfor %}
130+
</div>
131+
132+
;
133+
134+
{% if data.neetcode_solution %}
135+
<h3>Solution Code ({{ data.language }})</h3>
136+
<pre><code>
137+
{{ data.neetcode_solution }}
138+
</code></pre>
139+
{% endif %}
140+
141+
{% if data.solutions %}
142+
<div class="community-solutions">
143+
<h3>Community Solutions:</h3>
144+
<ul>
145+
{% for solution in data.solutions[:3] %}
146+
<li><a href="{{ solution_url(slug, solution.id) }}">Solution {{ loop.index }}</a></li>
147+
{% endfor %}
148+
</ul>
149+
</div>
150+
{% endif %}
151+
152+
;
153+
154+
{{ data.tags|map(attribute='slug')|join(' ') }}
155+
```
156+
72157
## 🛣 Roadmap
73158
- [X] Use TQDM to show card generation progress
74159
- [X] Add support for exporting to an excel sheet
75160
- [X] Add support for showing neetcode solutions on the back of the card as a
76161
- [X] Add support for getting the difficulty of questions
77-
- [ ] Add support for Jinja templating formatters
162+
- [X] Add support for Jinja templating formatters
163+
- [X] Add support for including NeetCode solution code
78164
- [ ] Add NeetCode shorts
79165
- [ ] Add support for fetching premium questions via authentification
80166
- [ ] Add support for importing cards into Quizlet

leetcode_study_tool/cli.py

+15
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ def generate_parser() -> argparse.ArgumentParser:
5757
help="The format to save the Leetcode problem(s) in.",
5858
)
5959

60+
parser.add_argument(
61+
"--template",
62+
"-t",
63+
type=str,
64+
help="Path to a custom Jinja template file for rendering problems.",
65+
)
66+
6067
parser.add_argument(
6168
"--csrf",
6269
"-c",
@@ -77,6 +84,14 @@ def generate_parser() -> argparse.ArgumentParser:
7784
"-l",
7885
type=str,
7986
help="The language to generate problem(s) for.",
87+
default="python",
88+
)
89+
90+
parser.add_argument(
91+
"--include-code",
92+
"-ic",
93+
action="store_true",
94+
help="Include solution code from NeetCode GitHub repository using the specified language.",
8095
)
8196

8297
return parser

leetcode_study_tool/creator.py

+24-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@
1010
from leetcode_study_tool.formatters import FORMAT_MAP
1111
from leetcode_study_tool.outputs import SAVE_MAP
1212
from leetcode_study_tool.presets import PRESET_MAP
13-
from leetcode_study_tool.queries import generate_session, get_data, get_slug
13+
from leetcode_study_tool.queries import (
14+
generate_session,
15+
get_data,
16+
get_neetcode_solution,
17+
get_slug,
18+
)
1419

1520

1621
class ProblemsCreator:
@@ -19,9 +24,10 @@ class ProblemsCreator:
1924
"""
2025

2126
def __init__(self, args: Union[argparse.Namespace, dict]) -> None:
22-
# Explicitly define for linting
2327
self.format = "anki"
2428
self.output = "output"
29+
self.template = None
30+
self.include_code = False
2531

2632
args = vars(args)
2733
for key in args:
@@ -53,12 +59,6 @@ def create_problems(self) -> None:
5359
"""
5460
problems = p_map(self._generate_problem, self.urls)
5561

56-
# with Pool() as pool:
57-
# problems = pool.map(
58-
# self._generate_problem,
59-
# self.urls,
60-
# )
61-
6262
self._save_output(problems, self.output)
6363

6464
def _sanitize(self, input: Union[str, list, None]) -> Union[str, list]:
@@ -82,8 +82,6 @@ def _sanitize(self, input: Union[str, list, None]) -> Union[str, list]:
8282
if isinstance(input, list):
8383
return input
8484
input = html.unescape(input)
85-
# input = input.replace(";", " ")
86-
# input = input.replace("\n", " ")
8785
input = re.sub(r"[;\n\r]", " ", input)
8886
input = input.replace("</strong>", "</strong><br>")
8987
input = re.sub(r"(<br>){2,}", "<br>", input)
@@ -104,8 +102,6 @@ def _generate_problem(self, url: str) -> Union[str, None]:
104102
---------
105103
url : str
106104
The URL of the question to generate a problem for.
107-
language : str
108-
The coding language to generate a problem for.
109105
110106
Returns
111107
-------
@@ -118,10 +114,25 @@ def _generate_problem(self, url: str) -> Union[str, None]:
118114
slug = get_slug(url)
119115
try:
120116
data = get_data(slug, self.language, self.session)
117+
118+
if (
119+
self.include_code
120+
and self.language
121+
and not data.get("neetcode_solution")
122+
):
123+
github_solution = get_neetcode_solution(
124+
data["id"], data["title"], self.language
125+
)
126+
if github_solution:
127+
data["neetcode_solution"] = github_solution
128+
121129
except Exception as e:
122130
print(f"Failed to generate problem for {url}: {e}")
123131
return None
124132

125133
data = {k: self._sanitize(v) for k, v in data.items()}
126134

127-
return FORMAT_MAP[self.format](url, slug, data) # type: ignore
135+
if self.format == "anki":
136+
return FORMAT_MAP[self.format](url, slug, data, self.template) # type: ignore
137+
else:
138+
return FORMAT_MAP[self.format](url, slug, data) # type: ignore

0 commit comments

Comments
 (0)