Skip to content

Commit aefa4c7

Browse files
committed
Added docstrings
1 parent 358501f commit aefa4c7

File tree

14 files changed

+870
-39
lines changed

14 files changed

+870
-39
lines changed

aoc/models/base.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@
66

77

88
class SolutionBase:
9+
"""
10+
Base class for implementing Advent of Code puzzle solutions with benchmarking capabilities.
11+
12+
This class provides a framework for solving daily AoC challenges, supporting both test and
13+
puzzle inputs, with optional performance benchmarking. Each puzzle solution should inherit
14+
from this class and implement `part1()` and `part2()` methods.
15+
16+
Attributes:
17+
day (int): The Advent of Code day number (-1 by default)
18+
part_num (int): The puzzle part number (1 or 2)
19+
is_raw (bool): Flag indicating whether to use raw input format
20+
skip_test (bool): Flag indicating whether to use actual puzzle input instead of test input
21+
_benchmark (bool): Flag for enabling solution timing measurements
22+
benchmark_times (list): List storing benchmark timestamps
23+
data: Puzzle input data loaded from either test or actual input files
24+
"""
25+
926
def __init__(
1027
self,
1128
day: int = -1,
@@ -14,6 +31,16 @@ def __init__(
1431
skip_test: bool = True,
1532
benchmark: bool = False,
1633
) -> None:
34+
"""
35+
Initialize a solution instance for an Advent of Code puzzle.
36+
37+
Args:
38+
day (int, optional): The day number of the puzzle (1-25). Defaults to -1.
39+
part_num (int, optional): Which part of the day's puzzle (1 or 2). Defaults to 1.
40+
is_raw (bool, optional): Whether to load input as raw text. Defaults to `False`.
41+
skip_test (bool, optional): Whether to use puzzle input instead of test input. Defaults to `True`.
42+
benchmark (bool, optional): Whether to measure solution execution time. Defaults to `False`.
43+
"""
1744
self.day = day
1845
self.part_num = part_num
1946
self.is_raw = is_raw
@@ -27,11 +54,29 @@ def __init__(
2754
)
2855

2956
def check_is_raw(self) -> None:
57+
"""
58+
Verify if raw input mode is enabled for puzzles that require it.
59+
60+
Some Advent of Code puzzles require preserving whitespace or special characters
61+
in the input. This method ensures raw mode is enabled for such puzzles.
62+
63+
Exits the program with a warning message if raw input mode is not enabled.
64+
"""
3065
if self.is_raw is False:
3166
logger.info("Please use --raw flag in this puzzle")
3267
exit()
3368

3469
def benchmark(self, _print: bool = False) -> None:
70+
"""
71+
Record or display solution execution time benchmarks.
72+
73+
When called with `_print=False`, records a timestamp. When called with `_print=True`,
74+
calculates and displays the elapsed time since the last timestamp in appropriate
75+
units (`s`, `ms`, `µs`, or `ns`).
76+
77+
Args:
78+
_print (bool, optional): Whether to print the elapsed time. Defaults to `False`.
79+
"""
3580
if (
3681
_print
3782
and len(self.benchmark_times) > 0
@@ -51,6 +96,21 @@ def benchmark(self, _print: bool = False) -> None:
5196
self.benchmark_times.append(timeit.default_timer())
5297

5398
def solve(self, part_num: int) -> int:
99+
"""
100+
Execute the solution for a specified puzzle part.
101+
102+
Calls either `part1()` or `part2()` method based on the `part_num` parameter,
103+
with optional execution time measurement.
104+
105+
Args:
106+
part_num (int): Which part of the puzzle to solve (1 or 2)
107+
108+
Returns:
109+
int: The solution result for the specified puzzle part
110+
111+
Note:
112+
Inheriting classes must implement `part1()` and `part2()` methods.
113+
"""
54114
func = getattr(self, f"part{part_num}")
55115
self.benchmark()
56116

aoc/models/file.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,23 @@
1414

1515

1616
class File:
17+
"""
18+
Utility class for managing Advent of Code file operations and puzzle input retrieval.
19+
20+
This class provides static methods for handling file paths, session management,
21+
downloading puzzle/test inputs, and setting up solution files. It manages the
22+
directory structure and timing for puzzle input availability.
23+
"""
24+
1725
@staticmethod
1826
def get_path() -> str:
27+
"""
28+
Get the absolute path to the project directory.
29+
30+
Returns:
31+
str: Absolute path to either the directory containing the script or
32+
the script's directory if it is itself a directory.
33+
"""
1934
return (
2035
path
2136
if os.path.isdir(path := os.path.realpath(sys.argv[0]))
@@ -24,6 +39,15 @@ def get_path() -> str:
2439

2540
@staticmethod
2641
def get_session() -> str:
42+
"""
43+
Retrieve the Advent of Code session token from a local file.
44+
45+
Returns:
46+
str: The session token stripped of whitespace.
47+
48+
Note:
49+
Expects a file named `aoc_session` in the project root directory.
50+
"""
2751
session = ""
2852
path = File.get_path()
2953
session_path = os.path.realpath(f"{path}/aoc_session")
@@ -35,6 +59,15 @@ def get_session() -> str:
3559

3660
@staticmethod
3761
def get_headers() -> Dict[str, str]:
62+
"""
63+
Load HTTP headers configuration from a JSON file.
64+
65+
Returns:
66+
Dict[str, str]: Dictionary of HTTP headers for AoC API requests.
67+
68+
Note:
69+
Expects a file named `aoc_headers.json` in the project root directory.
70+
"""
3871
headers = {}
3972
path = File.get_path()
4073
headers_config_path = os.path.realpath(f"{path}/aoc_headers.json")
@@ -46,6 +79,19 @@ def get_headers() -> Dict[str, str]:
4679

4780
@staticmethod
4881
def download_puzzle_input(day: int) -> str:
82+
"""
83+
Download the puzzle input for a specific day from Advent of Code.
84+
85+
Args:
86+
day (int): The day number (1-25) of the puzzle.
87+
88+
Returns:
89+
str: The puzzle input content.
90+
91+
Note:
92+
Requires valid session token and headers configuration.
93+
Year is determined from the project directory name.
94+
"""
4995
session = File.get_session()
5096
year = File.get_path().split(os.sep)[-1].split("-")[-1]
5197

@@ -65,6 +111,20 @@ def download_puzzle_input(day: int) -> str:
65111

66112
@staticmethod
67113
def download_test_input(day: int, part_num: int) -> str | None:
114+
"""
115+
Download test input from the puzzle description for a specific day and part.
116+
117+
Args:
118+
day (int): The day number (1-25) of the puzzle.
119+
part_num (int): The puzzle part number (1 or 2).
120+
121+
Returns:
122+
str | None: The test input content if found, `None` if download fails.
123+
124+
Note:
125+
Extracts test input from code blocks in the puzzle description.
126+
Part number determines which code block to use.
127+
"""
68128
session = File.get_session()
69129
year = File.get_path().split(os.sep)[-1].split("-")[-1]
70130

@@ -91,6 +151,20 @@ def download_test_input(day: int, part_num: int) -> str | None:
91151

92152
@staticmethod
93153
def add_day(day: int) -> None:
154+
"""
155+
Set up the file structure for a new puzzle day.
156+
157+
Creates solution file from template and downloads puzzle input when available.
158+
Waits for puzzle unlock time (5:00 UTC) if necessary.
159+
160+
Args:
161+
day (int): The day number (1-25) to set up.
162+
163+
Note:
164+
Creates following structure:
165+
- `solutions/dayXX.py` (from template)
166+
- `data/dayXX/puzzle_input.txt`
167+
"""
94168
path = File.get_path()
95169
solution = os.path.realpath(f"{path}/solutions/day{day:02}.py")
96170
solution_path = Path(solution)
@@ -141,6 +215,19 @@ def add_day(day: int) -> None:
141215

142216
@staticmethod
143217
def add_test_input(day: int, part_num: int) -> None:
218+
"""
219+
Set up and download test input for a specific puzzle part.
220+
221+
Creates test input file and downloads content when available.
222+
Waits for puzzle unlock time (5:00 UTC) if necessary.
223+
224+
Args:
225+
day (int): The day number (1-25) of the puzzle.
226+
part_num (int): The puzzle part number (1 or 2).
227+
228+
Note:
229+
Creates file at: `data/dayXX/test_XX_input.txt`
230+
"""
144231
path = File.get_path()
145232
folder = os.path.realpath(f"{path}/data/day{day:02}")
146233
folder_path = Path(folder)
@@ -179,6 +266,19 @@ def add_test_input(day: int, part_num: int) -> None:
179266

180267
@staticmethod
181268
def add_test_file(day: int) -> None:
269+
"""
270+
Create a test file for a specific puzzle day from template.
271+
272+
Args:
273+
day (int): The day number (1-25) to create test file for.
274+
275+
Note:
276+
Creates test file at: `tests/test_XX.py` using template from `templates/tests/sample.txt`
277+
Replaces placeholders in template with actual day number.
278+
279+
Raises:
280+
FileNotFoundError: If the template file is not found.
281+
"""
182282
path = File.get_path()
183283
test = os.path.realpath(f"{path}/tests/test_{day:02}.py")
184284

@@ -197,7 +297,6 @@ def add_test_file(day: int) -> None:
197297
if content:
198298
logger.info("Content loaded successfully")
199299

200-
# Replace placeholders with actual values
201300
content = content.format(day=day)
202301
with open(test, "w+") as f:
203302
f.write(content)

aoc/models/reader.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,28 @@
44

55

66
class Reader:
7+
"""
8+
Utility class for reading Advent of Code puzzle and test input files.
9+
10+
This class handles file reading operations with support for both raw and stripped
11+
input formats. It maintains consistent file path handling relative to the project root.
12+
13+
Attributes:
14+
PROJECT_ROOT (str): Absolute path to the project root directory, determined from
15+
the `aoc` package location.
16+
"""
17+
718
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__).rsplit("aoc", 1)[0])
819

920
@staticmethod
1021
def get_path() -> str:
22+
"""
23+
Get the absolute path to the script's location.
24+
25+
Returns:
26+
str: Absolute path to either the directory containing the script or
27+
the script's directory if it is itself a directory.
28+
"""
1129
return (
1230
path
1331
if os.path.isdir(path := os.path.realpath(sys.argv[0]))
@@ -16,6 +34,20 @@ def get_path() -> str:
1634

1735
@staticmethod
1836
def get_puzzle_input(day: int, is_raw: bool) -> List[str]:
37+
"""
38+
Read in the puzzle input file for a specific day.
39+
40+
Args:
41+
day (int): The day number (1-25) of the puzzle.
42+
is_raw (bool): If True, preserves newlines. If False, strips all whitespace.
43+
44+
Returns:
45+
List[str]: List of input lines, processed according to `is_raw` flag.
46+
47+
Note:
48+
Expects input file at: `data/dayXX/puzzle_input.txt`
49+
where `XX` is the zero-padded day number.
50+
"""
1951
file_path = os.path.join(
2052
Reader.PROJECT_ROOT, f"data/day{day:02d}/puzzle_input.txt"
2153
)
@@ -27,6 +59,21 @@ def get_puzzle_input(day: int, is_raw: bool) -> List[str]:
2759

2860
@staticmethod
2961
def get_test_input(day: int, is_raw: bool, part_num: int) -> List[str]:
62+
"""
63+
Read the test input file for a specific day and puzzle part.
64+
65+
Args:
66+
day (int): The day number (1-25) of the puzzle.
67+
is_raw (bool): If `True`, preserves newlines. If `False`, strips all whitespace.
68+
part_num (int): The puzzle part number (1 or 2).
69+
70+
Returns:
71+
List[str]: List of test input lines, processed according to `is_raw` flag.
72+
73+
Note:
74+
Expects input file at: `data/dayXX/test_YY_input.txt`
75+
where `XX` is the zero-padded day number and `YY` is the zero-padded part number.
76+
"""
3077
file_path = os.path.join(
3178
Reader.PROJECT_ROOT, f"data/day{day:02d}/test_{part_num:02d}_input.txt"
3279
)

0 commit comments

Comments
 (0)