223 lines
5.8 KiB
Python
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())
|