aoc2023/aoc.py
2023-12-03 14:38:29 +01:00

141 lines
3.9 KiB
Python

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Generic, TypeVar
from urllib.request import Request, urlopen
from html.parser import HTMLParser
from importlib import import_module
T1 = TypeVar("T1")
T2 = TypeVar("T2")
class CodeExtractor(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.inpre = False
self.incode = False
self.code: list[str] = []
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
if tag == "code":
self.incode = True
elif tag == "pre":
self.inpre = True
def handle_endtag(self, tag: str) -> None:
if tag == "code":
self.incode = False
elif tag == "pre":
self.inpre = False
def handle_data(self, data: str) -> None:
if self.incode and self.inpre:
self.code.append(data)
class Aoc2(ABC, Generic[T1, T2]):
def __init__(self, year: int, day: int):
self._year = year
self._day = day
with open(".session", encoding="utf8") as f:
self._session = f.read().strip()
@abstractmethod
def part1(self, inpt: T1) -> Any:
...
@abstractmethod
def part2(self, inpt: T2) -> Any:
...
@abstractmethod
def parseinput1(self, inpt: str) -> T1:
...
@abstractmethod
def parseinput2(self, inpt: str) -> T2:
...
def dl_with_cache(self, url: str, cachefilepath: Path) -> str:
cachefilepath.parent.mkdir(parents=True, exist_ok=True)
if cachefilepath.exists():
return cachefilepath.read_text()
request = Request(url, headers={"Cookie": f"session={self._session}"})
with urlopen(request) as response:
inpt: str = response.read().decode()
cachefilepath.write_text(inpt)
return inpt
def get_data_for_day(self) -> str:
return self.dl_with_cache(
f"https://adventofcode.com/{self._year}/day/{self._day}/input",
Path("aoc_cache") / str(self._year) / f"day{self._day}_input",
)
def get_examples_for_day(self) -> list[str]:
site = self.dl_with_cache(
f"https://adventofcode.com/{self._year}/day/{self._day}",
Path("aoc_cache") / str(self._year) / f"day{self._day}_site",
)
code_extractor = CodeExtractor()
code_extractor.feed(site)
return code_extractor.code
def run_example_1(self, ex_nr: int = 0) -> str:
inpt = self.get_examples_for_day()[ex_nr]
return str(self.part1(self.parseinput1(inpt)))
def run_example_2(self, ex_nr: int = -1) -> str:
inpt = self.get_examples_for_day()[ex_nr]
return str(self.part2(self.parseinput2(inpt)))
def run_1(self) -> str:
inpt = self.get_data_for_day()
return str(self.part1(self.parseinput1(inpt)))
def run_2(self) -> str:
inpt = self.get_data_for_day()
return str(self.part2(self.parseinput2(inpt)))
def run(self) -> None:
print("Example:\t", end="")
print(f"{self.run_example_1()}\t", end="")
print(f"{self.run_example_2()}")
print("Real input:\t", end="")
print(f"{self.run_1()}\t", end="")
print(f"{self.run_2()}")
class Aoc(Generic[T1], Aoc2[T1, T1]):
def parseinput(self, inpt: str) -> T1:
raise NotImplementedError
def parseinput1(self, inpt: str) -> T1:
return self.parseinput(inpt)
def parseinput2(self, inpt: str) -> T1:
return self.parseinput(inpt)
def part2(self, inpt: T1) -> Any:
return self.part1(inpt)
def main():
days = [
import_module(file.stem)
for file in Path(".").iterdir()
if file.name.endswith(".py") and file.name.startswith("day")
]
for day in days:
print(f"Running {day.__name__}")
day.main()
print()
if __name__ == "__main__":
main()