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()