aoc/.util.py

223 lines
5.8 KiB
Python

# FUCK CODE CONVENTIONS.
# basic sanity checks
import sys
if sys.prefix == sys.base_prefix:
raise RuntimeError("Please run this and all scripts within virtualenv.")
import getpass
import json
import os
import subprocess
import warnings
from datetime import datetime, timedelta, timezone
from sys import executable
import click
import requests
from click import echo
@click.group()
def cg():
pass
TODAY = datetime.now(timezone(timedelta(hours=-5)))
class InvalidDayWarning(UserWarning):
pass
################################
##### ADD STUFF BELOW HERE #####
################################
tmpl_m = """from aoc_utils.log import debug
# copy and rename to main_2 for part 2.
def main(data: str):
debug("nothing here!")
... # write your logic here !
if __name__ == "__main__":
# shouldn't change anything here.
with open('input.txt') as f:
main(f.read().strip())
"""
tmpl_t = """from main{pt} import main
test_data = '''
{testdata}
'''
main(test_data.strip())
"""
def create_file(path, data=""):
print(f"creating {path}... ", end="")
with open(path, "w+") as f:
chars = f.write(data)
echo("done" + (f", wrote {chars} chars" if chars != 0 else ""))
def check_and_load(ctx) -> dict[str, str]:
"""Check if the configuration file is sane enough to use, then return the parsed dict.
:raises KeyError: raised if a required value doesn't exist.
:return:
:rtype: dict
"""
required_keys = ("token",)
echo("Loading configuration file...")
try:
with open("config.json") as f:
try:
config = json.load(f)
except json.JSONDecodeError as exc:
echo(f"ERROR: configuration file cannot be parsed: {exc}")
ctx.exit(1)
except (FileNotFoundError, PermissionError) as exc:
echo(f"ERROR: configuration file cannot be read: {exc}")
ctx.exit(1)
print("Checking if configuration is sane... ", end="")
for key in required_keys:
if not key in config:
raise KeyError(f"required key {key} not found in config.")
echo("OK")
# default values
config["year"] = config["year"] if "year" in config else TODAY.year
# OK, return the config now.
return config
@cg.command()
def setup():
"""Setup the environment."""
n_config = {}
echo("Setting up stuff...")
echo("You will be asked to provide some details.")
n_config["token"] = getpass.getpass(
"- Session token (find in devtools storage) <hidden> > "
)
n_config["year"] = click.prompt("- aoc year", default=TODAY.year)
print("\nSaving config... ", end="")
with open("config.json", "w+") as f:
d = json.dumps(n_config, indent=4)
b = f.write(d)
echo(f"OK config.json: wrote {b} chars.")
@cg.command()
@click.pass_context
@click.option("-d", "--day", default=TODAY.day)
@click.option("-p", "--part", default=1)
def test(ctx, day, part):
"""Test the code with the test function for a day"""
config = check_and_load(ctx)
if not (os.path.exists(f"./{config['year']}/{day}")):
echo("ERROR: day does not seem to exist.")
ctx.exit(1)
echo("Running test...")
subprocess.run(
[executable, f"test{'_2' if part == 2 else ''}.py"],
cwd=f"./{config['year']}/{day}",
env={
"PYTHONPATH": os.path.dirname(os.path.abspath(__file__)),
"DEBUG": os.getenv("DEBUG"),
},
)
@cg.command()
@click.pass_context
@click.option("-d", "--day", default=TODAY.day)
@click.option("-p", "--part", default=1)
def run(ctx, day, part):
"""Run the script for a day."""
config = check_and_load(ctx)
if not (os.path.exists(f"./{config['year']}/{day}")):
echo("ERROR: day does not seem to exist.")
ctx.exit(1)
echo("Running code...")
subprocess.run(
[executable, f"main{'_2' if part == 2 else ''}.py"],
cwd=f"./{config['year']}/{day}",
env={
"PYTHONPATH": os.path.dirname(os.path.abspath(__file__)),
"DEBUG": os.getenv("DEBUG"),
},
)
@cg.command()
@click.pass_context
@click.option("-d", "--day", default=TODAY.day)
def new(ctx, day):
"""Initialize a new day"""
config = check_and_load(ctx)
if day not in range(1, 26):
raise ValueError(
"Advent of Code only has 25 days. "
"If you're running this without arguments, try specifying the day manually."
)
elif day > TODAY.day:
warnings.warn(
f"day {day} may not be available yet ({day} > today ({TODAY.day}))! proceed with caution and don't spam this."
)
echo("Paste in the test data:")
tests = ""
while (line := input("> ")) != "":
tests += line + "\n"
tests = tests.strip()
echo(f"Fetching puzzle input for day {day}...")
res = requests.get(
f"https://adventofcode.com/{config['year']}/day/{day}/input",
cookies={"session": config["token"]},
)
if res.status_code == 404 or res.status_code == 400:
echo("ERROR: this day is not available yet, or your token expired.")
echo(f" response: [{res.status_code}] {res.text[0:100]}")
ctx.exit(1)
res.raise_for_status()
data = res.text
echo(f"acquired data. took {res.elapsed}. total chars: {len(data)}")
echo(f"initializing directory...")
os.makedirs(f'./{config["year"]}/{day}', exist_ok=True)
create_file(f'./{config["year"]}/{day}/input.txt', data)
create_file(f'./{config["year"]}/{day}/main.py', tmpl_m)
create_file(
f'./{config["year"]}/{day}/test.py', tmpl_t.format(pt="", testdata=tests)
)
create_file(
f'./{config["year"]}/{day}/test_2.py', tmpl_t.format(pt="_2", testdata=tests)
)
def main() -> int | None:
cg(prog_name="make")
if __name__ == "__main__":
sys.exit(main())