195 lines
5.3 KiB
Python
195 lines
5.3 KiB
Python
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Any, Generic, Protocol, 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, example_code_nr1: int = 0, example_code_nr2: int = -1
|
|
):
|
|
self._year = year
|
|
self._day = day
|
|
self._example_code_nr1 = example_code_nr1
|
|
self._example_code_nr2 = example_code_nr2
|
|
|
|
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) -> str:
|
|
inpt = self.get_examples_for_day()[self._example_code_nr1]
|
|
return str(self.part1(self.parseinput1(inpt)))
|
|
|
|
def run_example_2(self) -> str:
|
|
inpt = self.get_examples_for_day()[self._example_code_nr2]
|
|
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)
|
|
|
|
|
|
class AocSameParser(Generic[T1]):
|
|
@abstractmethod
|
|
def parseinput(self, inpt: str) -> T1:
|
|
...
|
|
|
|
def parseinput1(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
def parseinput2(self, inpt: str) -> T1:
|
|
return self.parseinput(inpt)
|
|
|
|
|
|
class AocSameImplementation(Generic[T1]):
|
|
@abstractmethod
|
|
def part(self, inpt: T1) -> Any:
|
|
...
|
|
|
|
def part1(self, inpt: T1) -> Any:
|
|
return self.part(inpt)
|
|
|
|
def part2(self, inpt: T1) -> Any:
|
|
return self.part(inpt)
|
|
|
|
|
|
class AocParseLines(Generic[T1, T2], Protocol):
|
|
def parseline1(self, inpt: str) -> T1:
|
|
raise NotImplementedError
|
|
|
|
def parseinput1(self, inpt: str) -> list[T1]:
|
|
return [self.parseline1(line) for line in inpt.splitlines()]
|
|
|
|
def parseline2(self, inpt: str) -> T2:
|
|
raise NotImplementedError
|
|
|
|
def parseinput2(self, inpt: str) -> list[T2]:
|
|
return [self.parseline2(line) for line in inpt.splitlines()]
|
|
|
|
|
|
class AocParseLinesSameParser(Generic[T1], AocParseLines[T1, T1]):
|
|
def parseline1(self, inpt: str) -> T1:
|
|
return self.parseline(inpt)
|
|
|
|
def parseline2(self, inpt: str) -> T1:
|
|
return self.parseline(inpt)
|
|
|
|
@abstractmethod
|
|
def parseline(self, inpt: str) -> T1:
|
|
...
|
|
|
|
|
|
def main() -> None:
|
|
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()
|