From 1623281d90ea7189914f45730fc7b0f7519e94cb Mon Sep 17 00:00:00 2001 From: Tyler Hoffman Date: Thu, 23 Nov 2023 13:52:25 -0500 Subject: [PATCH] Boilerplate (#2) * Add boilerplate * Fix typing --- utils/__init__.py | 0 utils/create_files.py | 67 +++++++++++++++++++++++++++++++++ utils/file_data.py | 72 ++++++++++++++++++++++++++++++++++++ utils/submit.py | 37 ++++++++++++++++++ utils/templates/__init__.py | 0 utils/templates/parser.py | 11 ++++++ utils/templates/part.py | 39 +++++++++++++++++++ utils/templates/test_part.py | 30 +++++++++++++++ 8 files changed, 256 insertions(+) create mode 100644 utils/__init__.py create mode 100644 utils/create_files.py create mode 100644 utils/file_data.py create mode 100644 utils/submit.py create mode 100644 utils/templates/__init__.py create mode 100644 utils/templates/parser.py create mode 100644 utils/templates/part.py create mode 100644 utils/templates/test_part.py diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/create_files.py b/utils/create_files.py new file mode 100644 index 0000000..b077791 --- /dev/null +++ b/utils/create_files.py @@ -0,0 +1,67 @@ +import argparse +import os + +import aocd + +from utils.file_data import FileData +from utils.templates.parser import create_parser_stub +from utils.templates.part import create_part_stub +from utils.templates.test_part import create_part_test_stub + + +def touch_file(path: str) -> None: + open(path, "x") + + +def write_file(path: str, content: str) -> None: + with open(path, "w") as f: + f.write(content.strip() + "\n") + + +def create_directories_if_needed(file_data: FileData) -> None: + if not os.path.isdir(file_data.directory): + os.makedirs(file_data.directory) + touch_file(file_data.src_init_file) + write_file(file_data.input_file, aocd.get_data(year=2023, day=file_data.day)) + write_file(file_data.parser_file, create_parser_stub()) + write_file(file_data.prompt_file, "") + + if not os.path.isdir(file_data.test_directory): + os.makedirs(file_data.test_directory) + touch_file(file_data.test_init_file) + + +def create_part_files(file_data: FileData) -> None: + write_file( + file_data.part_file, + create_part_stub(day_string=file_data.day_string, part=file_data.part), + ) + + write_file( + file_data.test_part_file, + create_part_test_stub(day_string=file_data.day_string, part=file_data.part), + ) + + +def create_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Helper to bootstrap files for problems" + ) + parser.add_argument("-d", "--day", type=int, help="Day to create files for") + parser.add_argument( + "-p", + "--part", + choices=["a", "b"], + help="Part to create files for", + ) + + return parser + + +if __name__ == "__main__": + args = create_parser().parse_args() + + file_data = FileData(day=args.day, part=args.part) + + create_directories_if_needed(file_data) + create_part_files(file_data) diff --git a/utils/file_data.py b/utils/file_data.py new file mode 100644 index 0000000..7875220 --- /dev/null +++ b/utils/file_data.py @@ -0,0 +1,72 @@ +import dataclasses +from typing import Literal, Union + +Part = Union[Literal["a"], Literal["b"]] + + +@dataclasses.dataclass +class FileData(object): + """Dataclass to handle any directory/file names + to be used when bootstrapping files + """ + + src = "aoc_2023" + tests = "tests" + + day: int + part: Part + + @property + def day_string(self) -> str: + return f"{self.day:02d}" + + @property + def part_module(self) -> str: + return f"{self.src}.day_{self.day_string}.{self.part}" + + @property + def directory(self) -> str: + return f"{self.src}/day_{self.day_string}" + + @property + def test_directory(self) -> str: + return f"{self.tests}/test_day_{self.day_string}" + + @property + def input_file(self) -> str: + return f"{self.directory}/input.txt" + + @property + def part_file(self) -> str: + return f"{self.directory}/{self.part}.py" + + @property + def parser_file(self) -> str: + return f"{self.directory}/parser.py" + + @property + def prompt_file(self) -> str: + return f"{self.directory}/from_prompt.py" + + @property + def solver_file(self) -> str: + return f"{self.directory}/solver.py" + + @property + def test_part_file(self) -> str: + return f"{self.test_directory}/test_{self.part}.py" + + @property + def test_data_file(self) -> str: + return f"{self.test_directory}/sample_data.py" + + @property + def src_init_file(self) -> str: + return self._init_file(self.directory) + + @property + def test_init_file(self) -> str: + return self._init_file(self.test_directory) + + def _init_file(self, directory: str) -> str: + return f"{directory}/__init__.py" diff --git a/utils/submit.py b/utils/submit.py new file mode 100644 index 0000000..d8abf63 --- /dev/null +++ b/utils/submit.py @@ -0,0 +1,37 @@ +import argparse +import subprocess + +import aocd + +from utils.file_data import FileData, Part + + +def submit(day: int, part: Part) -> None: + data_file = FileData(day=day, part=part) + completed_process = subprocess.run( + ["python", "-m", data_file.part_module], capture_output=True + ) + answer = completed_process.stdout.decode("utf-8").strip() + + print(f"Submitting answer: {answer}") + assert not isinstance(aocd.submit, str) + aocd.submit(day=day, part=part, year=2023, answer=answer) + + +def create_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Helper to submit problems") + parser.add_argument("-d", "--day", type=int, help="Day to create files for") + parser.add_argument( + "-p", + "--part", + choices=["a", "b"], + help="Part to create files for", + ) + + return parser + + +if __name__ == "__main__": + args = create_parser().parse_args() + + submit(day=args.day, part=args.part) diff --git a/utils/templates/__init__.py b/utils/templates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/templates/parser.py b/utils/templates/parser.py new file mode 100644 index 0000000..0db5bcd --- /dev/null +++ b/utils/templates/parser.py @@ -0,0 +1,11 @@ +def create_parser_stub() -> str: + return _PARSER_TEMPLATE + + +_PARSER_TEMPLATE = """ +class Parser: + @staticmethod + def parse(input: str) -> str: + return input.strip() + +""" diff --git a/utils/templates/part.py b/utils/templates/part.py new file mode 100644 index 0000000..bd43420 --- /dev/null +++ b/utils/templates/part.py @@ -0,0 +1,39 @@ +def create_part_stub(day_string: str, part: str) -> str: + src = "aoc_2023" + return _PART_TEMPLATE.format( + day_string=day_string, + part_upper=part.upper(), + src=src, + ) + + +_PART_TEMPLATE = """ +from dataclasses import dataclass +from {src}.day_{day_string}.parser import Parser + + +@dataclass +class Day{day_string}Part{part_upper}Solver: + input: str + + @property + def solution(self) -> int: + return -1 + + +def solve(input: str) -> int: + data = Parser.parse(input) + solver = Day{day_string}Part{part_upper}Solver(data) + + return solver.solution + +def get_solution() -> int: + with open("aoc_2023/day_{day_string}/input.txt", "r") as f: + input = f.read() + return solve(input) + + +if __name__ == "__main__": + print(get_solution()) + +""" diff --git a/utils/templates/test_part.py b/utils/templates/test_part.py new file mode 100644 index 0000000..48ff401 --- /dev/null +++ b/utils/templates/test_part.py @@ -0,0 +1,30 @@ +def create_part_test_stub(day_string: str, part: str) -> str: + src = "aoc_2023" + return _TEST_PART_TEMPLATE.format( + day_string=day_string, + part=part, + part_upper=part.upper(), + src=src, + ) + + +_TEST_PART_TEMPLATE = """ +import pytest + +from {src}.day_{day_string}.{part} import get_solution, solve + + +@pytest.mark.parametrize( + "input, expected", + [ + ("INPUT", -1), + ], +) +def test_solve(input: str, expected: int): + assert solve(input) == expected + +@pytest.mark.skip +def test_my_solution(): + assert get_solution() == "NOT THIS" + +"""