Skip to content

Commit d43f367

Browse files
committed
Update tiles script to include custom function to retrieve solution paths
1 parent 35a9b47 commit d43f367

File tree

3 files changed

+95
-41
lines changed

3 files changed

+95
-41
lines changed

AoCTiles/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ Copy this entire folder into your AoC repository. Then install the requirements.
1919

2020
### Requirements
2121

22-
Install at least Python 3.9.
22+
Install at least Python 3.10 (because of the new typing union `|`, if you cannot do so you can
23+
change the `type1 | type2` annotations to `Union[type1, type2]`).
2324

24-
Install requirements:
25+
Install the requirements (`pillow` for creating images, `requests` for the leaderboard data and `pyyaml` to parse the language config):
2526

2627
```
2728
pip install -r requirements.txt
@@ -34,6 +35,10 @@ It should contain a single line, the "session" cookie
3435
when logged in to https://adventofcode.com. Just
3536
paste it in there.
3637

38+
Now add `<!-- AOC TILES BEGIN -->` and `<!-- AOC TILES END -->` in your README, everything between these 2 tags
39+
will always be replaced by the script, so do not add additional information there. Note that these are HTML comments,
40+
so they will not be visible in the rendered README.
41+
3742
Your year/day script structure likely looks different. You can change the patterns for years and days in the
3843
[create_aoc_tiles.py](create_aoc_tiles.py) file.
3944

AoCTiles/create_aoc_tiles.py

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@
2929
from PIL.ImageDraw import ImageDraw
3030
from PIL import ImageFont
3131

32-
# The year and day pattern to detect directories. For example, if your day folders are
33-
# called "day1" to "day25" then set the pattern to r"day\d{1,2}". The script extracts
34-
# a number from the folder and tries to guess its day that way.
35-
YEAR_PATTERN = r"\d{4}"
36-
DAY_PATTERN = r"\d{2}"
37-
3832
# This results in the parent directory of the script directory, the year directories should be here
3933
AOC_DIR = Path(__file__).absolute().parent.parent
4034

@@ -56,6 +50,55 @@
5650
# it was completed with a checkmark
5751
SHOW_CHECKMARK_INSTEAD_OF_TIME_RANK = False
5852

53+
# The year and day pattern to detect directories. For example, if your day folders are
54+
# called "day1" to "day25" then set the pattern to r"day\d{1,2}". The script extracts
55+
# a number from the folder and tries to guess its day that way.
56+
YEAR_PATTERN = r"\d{4}"
57+
DAY_PATTERN = r"\d{2}"
58+
59+
60+
# You can change this code entirely, or just change patterns above. You get more control if you change the code.
61+
def get_solution_paths_dict_for_years() -> dict[int, dict[int, list[str]]]:
62+
"""Returns a dictionary which maps years to days to a list of solution paths,
63+
64+
E.g.: {2022: {1: [Path("2022/01/01.py"), Path("2022/01/01.kt")], ...}}
65+
66+
This functions gives you more control of which solutions should be shown in the tiles. For example, you
67+
can filter by extension, or only show a single solution, or show tiles for days that have been completed
68+
but do not have a solution.
69+
70+
These can also be links to external solutions, e.g. if you want to show a solution from a different repository.
71+
(Untested however)
72+
73+
"""
74+
solution_paths_dict: dict[int, dict[int, list[str]]] = {}
75+
76+
# If you use a new repo for years you might just remove this if, and assign the year manually
77+
for year_dir in sorted(get_paths_matching_regex(AOC_DIR, YEAR_PATTERN), reverse=True):
78+
year = find_first_number(year_dir.name)
79+
solution_paths_dict[year] = {}
80+
# If you have a deep structure then you can adjust the year dir as well:
81+
# year_dir = year_dir / "src/main/kotlin/com/example/aoc"
82+
for day_dir in get_paths_matching_regex(year_dir, DAY_PATTERN):
83+
day = find_first_number(day_dir.name)
84+
solutions = sorted(find_recursive_solution_files(day_dir))
85+
86+
# To filter by extension:
87+
# solutions = [s for s in solutions if s.suffix == ".py"]
88+
89+
# To only show a single solution:
90+
# solutions = [solutions[0]]
91+
92+
# To show tiles for days that have been completed but do not have a solution:
93+
# if len(solutions) == 0:
94+
# solutions = [Path("dummy.kt")]
95+
96+
solutions = [solution.relative_to(AOC_DIR) for solution in solutions]
97+
98+
solution_paths_dict[year][day] = [str(s) for s in solutions]
99+
return solution_paths_dict
100+
101+
59102
# ======================================================
60103
# === The following likely do not need to be changed ===
61104
# ======================================================
@@ -116,7 +159,15 @@ def get_paths_matching_regex(path: Path, pattern: str):
116159
return sorted([p for p in path.iterdir() if re.fullmatch(pattern, p.name)])
117160

118161

119-
def parse_leaderboard(leaderboard_path: Path) -> dict[str, DayScores]:
162+
def find_recursive_solution_files(directory: Path) -> list[Path]:
163+
solution_paths = []
164+
for path in directory.rglob('*'):
165+
if path.is_file() and path.suffix in extension_to_color:
166+
solution_paths.append(path)
167+
return solution_paths
168+
169+
170+
def parse_leaderboard(leaderboard_path: Path) -> dict[int, DayScores]:
120171
no_stars = "You haven't collected any stars... yet."
121172
start = '<span class="leaderboard-daydesc-both"> *Time *Rank *Score</span>\n'
122173
end = "</pre>"
@@ -134,11 +185,11 @@ def parse_leaderboard(leaderboard_path: Path) -> dict[str, DayScores]:
134185
scores = [s if s != "-" else None for s in scores]
135186
assert len(scores) in (
136187
3, 6), f"Number scores for {day=} ({scores}) are not 3 or 6."
137-
leaderboard[day] = DayScores(*scores)
188+
leaderboard[int(day)] = DayScores(*scores)
138189
return leaderboard
139190

140191

141-
def request_leaderboard(year: int) -> dict[str, DayScores]:
192+
def request_leaderboard(year: int) -> dict[int, DayScores]:
142193
leaderboard_path = CACHE_DIR / f"leaderboard{year}.html"
143194
if leaderboard_path.exists():
144195
leaderboard = parse_leaderboard(leaderboard_path)
@@ -278,49 +329,48 @@ def generate_day_tile_image(day: str, year: str, languages: list[str], day_score
278329
return path
279330

280331

281-
def handle_day(day: int, year: int, day_path: Path, html: HTML, day_scores: DayScores | None):
332+
def handle_day(day: int, year: int, solutions: list[str], html: HTML, day_scores: DayScores | None):
282333
languages = []
283-
solution_file_path = None
284-
if day_path is not None:
285-
for file_path in day_path.glob("*"):
286-
if file_path.is_file():
287-
if file_path.suffix.lower() in extension_to_color:
288-
if solution_file_path is None:
289-
solution_file_path = file_path.relative_to(AOC_DIR)
290-
languages.append(file_path.suffix.lower())
291-
languages = sorted(set(languages))
334+
for solution in solutions:
335+
extension = "." + solution.split(".")[-1]
336+
if extension in extension_to_color and extension not in languages:
337+
languages.append(extension)
338+
solution_link = solutions[0] if solutions else None
292339
if DEBUG:
293340
if day == 25:
294341
languages = []
295342
day_graphic_path = generate_day_tile_image(f"{day:02}", f"{year:04}", languages, day_scores)
296343
day_graphic_path = day_graphic_path.relative_to(AOC_DIR)
297-
with html.tag("a", href=str(solution_file_path)):
344+
with html.tag("a", href=str(solution_link)):
298345
html.tag("img", closing=False, src=str(day_graphic_path), width=TILE_WIDTH_PX)
299346

300347

301348
def find_first_number(string: str) -> int:
302349
return int(re.findall(r"\d+", string)[0])
303350

304351

305-
def handle_year(year_path: Path, year: int):
352+
def fill_empty_days_in_dict(day_to_solutions: dict[int, list[str]], max_day) -> None:
353+
if not CREATE_ALL_DAYS and len(day_to_solutions) == 0:
354+
print(f"Current year has no solutions!")
355+
for day in range(1, max_day + 1):
356+
if day not in day_to_solutions:
357+
day_to_solutions[day] = []
358+
359+
360+
def handle_year(year: int, day_to_solutions: dict[int, list[str]]):
306361
leaderboard = request_leaderboard(year)
307362
if DEBUG:
308-
leaderboard["25"] = None
309-
leaderboard["24"] = DayScores("22:22:22", "12313", "0")
363+
leaderboard[25] = None
364+
leaderboard[24] = DayScores("22:22:22", "12313", "0")
365+
day_to_solutions[23] = []
310366
html = HTML()
311367
with html.tag("h1", align="center"):
312368
stars = sum((ds.time1 is not None) + (ds.time2 is not None) for ds in leaderboard.values() if ds is not None)
313369
html.push(f"{year} - {stars} ⭐")
314-
days_with_filled_gaps = {find_first_number(p.name): p for p in get_paths_matching_regex(year_path, DAY_PATTERN)}
315-
if not CREATE_ALL_DAYS and len(days_with_filled_gaps) == 0:
316-
print(f"Year {year} is empty!")
317-
return
318-
max_day = 25 if CREATE_ALL_DAYS else max(*days_with_filled_gaps, *map(int, leaderboard))
319-
for day in range(1, max_day + 1):
320-
if day not in days_with_filled_gaps:
321-
days_with_filled_gaps[day] = None
322-
for day, day_path in days_with_filled_gaps.items():
323-
handle_day(day, year, day_path, html, leaderboard.get(str(day), None))
370+
max_day = 25 if CREATE_ALL_DAYS else max(*day_to_solutions, *leaderboard)
371+
fill_empty_days_in_dict(day_to_solutions, max_day)
372+
for day, solutions in day_to_solutions.items():
373+
handle_day(day, year, solutions, html, leaderboard.get(day, None))
324374

325375
with open(README_PATH, "r") as file:
326376
text = file.read()
@@ -334,10 +384,9 @@ def handle_year(year_path: Path, year: int):
334384

335385

336386
def main():
337-
for year_path in sorted(get_paths_matching_regex(AOC_DIR, YEAR_PATTERN), reverse=True):
338-
year = find_first_number(year_path.name)
387+
for year, day_to_solutions_list in get_solution_paths_dict_for_years().items():
339388
print(f"=== Generating table for year {year} ===")
340-
handle_year(year_path, year)
389+
handle_year(year, day_to_solutions_list)
341390

342391

343392
if __name__ == "__main__":

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<h1 align="center">
77
2022 - 2 ⭐
88
</h1>
9-
<a href="2022/01/01.py">
9+
<a href="2022/01/01.kt">
1010
<img src="Media/2022/01.png" width="161px">
1111
</a>
1212
<h1 align="center">
@@ -75,13 +75,13 @@
7575
<a href="2021/21/21.jl">
7676
<img src="Media/2021/21.png" width="161px">
7777
</a>
78-
<a href="2021/22/part1.jl">
78+
<a href="2021/22/22.jl">
7979
<img src="Media/2021/22.png" width="161px">
8080
</a>
8181
<a href="2021/23/23.jl">
8282
<img src="Media/2021/23.png" width="161px">
8383
</a>
84-
<a href="2021/24/24makecpp.jl">
84+
<a href="2021/24/24.cpp">
8585
<img src="Media/2021/24.png" width="161px">
8686
</a>
8787
<a href="2021/25/25.jl">

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy