Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
176 commits
Select commit Hold shift + click to select a range
1563d01
Move input getter to new solution runner directory
Dec 3, 2021
51ccb61
Create Python packages requirements file
Dec 3, 2021
3484058
Add `requests` dependency to packages requirements file
Dec 3, 2021
b892fb7
Add `click` dependency to packages requirements file
Dec 3, 2021
a60498d
Create file holding CLI
Dec 3, 2021
1d62c60
Create constants file
Dec 3, 2021
f3101e4
Create CLI constants class
Dec 3, 2021
fcce4ed
Add CLI context constants
Dec 3, 2021
d82c13e
Create CLI
Dec 3, 2021
206d74c
Add file holding defaults and choices functions
Dec 3, 2021
68280f0
Move default year function to new file
Dec 3, 2021
dc56b01
Create setup command
Dec 3, 2021
b43f2b4
Add year option for setup command
Dec 3, 2021
80c1cb1
Add day option for setup command
Dec 3, 2021
3a21fa7
Add option to ignore cached input file
Dec 3, 2021
ca658e0
Add Python file extension constant
Dec 3, 2021
48bdddb
Add Python file extension
Dec 3, 2021
9ae190e
Add commands files paths constant
Dec 3, 2021
024436e
Add separators to module notation
Dec 3, 2021
14e83ae
Create Python module for filepaths and modules utils
Dec 3, 2021
b7d5a2e
Add function that converts a filepath to module form
Dec 3, 2021
06572e7
Add function that imports a module from a filepath
Dec 3, 2021
d07aaad
Use filename stem only instead of whole filename
Dec 3, 2021
84e8953
Move defaults and choices file into commands directory
Dec 4, 2021
26e2ea7
Create commands constants file
Dec 4, 2021
3e0dae0
Move "first AoC year" constant to commands constants file
Dec 4, 2021
45802a6
Change commands files globbing to accept special suffix only
Dec 4, 2021
f41bb5e
Add return type hint
Dec 8, 2021
2e11b7f
Move setup command to another file
Dec 8, 2021
bba1781
Add function that import a subcommand from a module
Dec 8, 2021
f55de9d
Create a main function for the solution runner
Dec 8, 2021
a7af43e
Add "should" to "use_cache" to clarify it's a boolean
Dec 8, 2021
c7e1f40
Make day option required
Dec 8, 2021
5370b5c
Add session ID to options list
Dec 8, 2021
12c4e2c
Add session ID to command parameters
Dec 8, 2021
afe0268
Add parameter name of use cache option
Dec 10, 2021
6571b77
Document environment variable for session ID in subcommand help
Dec 10, 2021
55a4beb
Add root directory to options list
Dec 10, 2021
3fda426
Add function that aborts script if input file exists and using cache
Dec 10, 2021
11d6e9a
Create file extensions list in constants file
Dec 10, 2021
6cb3e7a
Add Python to file extensions list
Dec 10, 2021
c703c29
Add function that creates the input and solutions files
Dec 10, 2021
a776f90
Add prompt if user didn't enter root directory
Dec 10, 2021
4b72816
Make root directory option required
Dec 10, 2021
2f493da
Change "challenge" to "puzzle"
Dec 10, 2021
e3d752a
Add constant holding US/Eastern timezone
Dec 10, 2021
fc71689
Add function that aborts the script if the puzzle isn't unlocked yet
Dec 10, 2021
b063f7b
Add function that asks user to create a directory if it doesn't exist
Dec 10, 2021
91852d9
Add function that downloads the challenge's input and write it to a file
Dec 11, 2021
97a4aa8
Add missing import statements
Dec 11, 2021
4f40076
Use click's `show_default`
Dec 11, 2021
e2f5b53
Add text file extension
Dec 11, 2021
38857bf
Remove suffix instead of calling `stem()`
Dec 18, 2021
936a9e0
Create class containing important directory paths
Dec 19, 2021
31e3e49
Add solutions directory path
Dec 19, 2021
623a455
Add input files directory path
Dec 19, 2021
eb71e53
Add import for unlock time template
Dec 23, 2021
a852789
Use same package import
Dec 23, 2021
011e595
Create config command
Dec 23, 2021
afa4c52
Add root directory parameter
Dec 23, 2021
61e974a
Add session ID parameter
Dec 23, 2021
3848c53
Add PyYAML to Python packages requirements file
Dec 23, 2021
d9c68dd
Add generic and cool config command
Dec 24, 2021
78789ea
Revert "Add generic and cool config command"
Dec 24, 2021
fd69ac0
Add root directory Click path
Dec 29, 2021
20864ed
Change root directory parameter type to new constant
Jan 6, 2022
44a729a
Add CLI app data directory that stores configuration file constant
Jan 6, 2022
eecd20c
Create app data directory variable
Jan 6, 2022
c422fda
Create configuration file name constant
Jan 6, 2022
db9e7bd
Create configuration file variable
Jan 6, 2022
52ca64f
Read from configuration file and create it and its directory if needed
Jan 6, 2022
da87fac
Save the initial configuration to see if it was edited afterwards
Jan 6, 2022
72239d4
Add root directory key name
Jan 6, 2022
eac5540
Add function that edits the root directory configuration
Jan 6, 2022
9972433
Create root directory if needed
Jan 6, 2022
6c1ecd3
Add hidden prompt for session ID
Jan 6, 2022
997c92b
Add session ID key name
Jan 6, 2022
0b8af2e
Add function that edits the session ID configuration
Jan 6, 2022
f7def1e
Configure session ID
Jan 6, 2022
2739e3a
Write new configuration to file if it was changed
Jan 6, 2022
cf536d2
Create command file lists from absolute path
Jan 6, 2022
a87c6bb
Temporarily changed to CLI directory so relative imports will work
Jan 6, 2022
57e8d18
Change import format so it'll work
Jan 6, 2022
382605e
Use absolute path of directory when processing relative path to it
Jan 6, 2022
71c62c7
Remove configuration options from `setup` command
Jan 6, 2022
226fc7c
Generalize utils file
Jan 6, 2022
3f1344a
Create function that returns a given key's configured value
Jan 7, 2022
24e98bb
Abort is requested puzzle is still locked
Jan 7, 2022
64e876b
Convert year to string
Jan 7, 2022
715254c
Convert day to string with leading zero if needed
Jan 7, 2022
412a5dc
Get root directory from configuration
Jan 7, 2022
999370f
Prompt use to create inputs directory if needed
Jan 7, 2022
5b16553
Prompt user to create year inputs directory if needed
Jan 7, 2022
9ede193
Create variable for day's input file
Jan 7, 2022
50a719b
Create input file
Jan 7, 2022
05594f2
Get session ID from configuration
Jan 7, 2022
227459d
Download input to file
Jan 7, 2022
bf976c1
Prompt user to create day's solutions directory if needed
Jan 7, 2022
0e618b8
Add Jinja to Python packages requirements file
Jan 15, 2022
edce6fc
Add template solution file
Jan 23, 2022
9e6fa67
Add constant holding solution file content
Jan 23, 2022
b8309e3
Write solution template to solution files instead of blank
Jan 23, 2022
6cbc754
Prompt user to create year solutions directory if needed
Jan 23, 2022
0c1770f
Create day solution files
Jan 23, 2022
0febc18
Revert "Add Jinja to Python packages requirements file"
Jan 23, 2022
553cd4f
Create commands utilities file
Jan 23, 2022
d6910b6
Move setting getter function into commands utils to simplify imports
Jan 23, 2022
92a3b40
Delete unused `COOKIE` constant
Jan 23, 2022
d6fb354
Extract "session" into constant
Jan 23, 2022
8a15463
Use `__file__` in solution file constant to convert to absolute path
Jan 23, 2022
078e449
Extract December month number into a constant
Jan 23, 2022
3bd174d
Extract Advent days range into a constant
Jan 23, 2022
a141d06
Extract solution parts into a constant
Jan 23, 2022
7b1aba3
Use `Iterable` instead of `Iterator` in type hint
katzuv Feb 3, 2022
0ac571e
Remove unneeded `else`
Feb 11, 2022
aba9582
Explicitly refer to the current working directory instead of `.`
Feb 11, 2022
8594d00
Use `os.sep` instead of explicitly declaring separators
Feb 11, 2022
f09a1b3
Cast `directory_relative_to` to `Path` if it is an `str`
Feb 11, 2022
b648ca1
Change type annotation from `Iterable` to `Iterator`
katzuv Oct 16, 2022
167a922
Remove dynamic commands loading code
katzuv Oct 16, 2022
f2769ac
Use right "default year" function
katzuv Oct 16, 2022
845ffec
Rearrange `import` statements
katzuv Oct 16, 2022
14a571a
Remove `main` function that contains only a single line
katzuv Oct 16, 2022
0845ed5
Replace `string.format` with `string.Template` for solution folder path
katzuv Oct 16, 2022
1848beb
Create submit command
katzuv Oct 16, 2022
5d902dd
Replace `string.format` with `string.Template` for input URL
katzuv Oct 16, 2022
06f0107
Remove obsolete "input getter" module
katzuv Oct 16, 2022
fb39ea3
Prepend "p" before solution part name
katzuv Oct 17, 2022
996e43b
Replace underscore with hyphen in `setup` command option
katzuv Oct 17, 2022
e6e70f6
Prepend puzzle solution directory with `d`
katzuv Oct 17, 2022
fec4225
Add `PathType` enum
katzuv Oct 17, 2022
1865258
Add function that checks whether a path exists and aborts if it does not
katzuv Oct 17, 2022
ced68d4
Add year option to `submit` command
katzuv Oct 30, 2022
22562d6
Add day option to submit command
katzuv Oct 30, 2022
2cecc69
Add part option to `submit` command
katzuv Oct 30, 2022
3ab890d
Convert year and day to `str`s
katzuv Oct 30, 2022
36046b1
Get puzzle solutions directory and abort if it doesn't exist
katzuv Oct 30, 2022
01eb7e6
Get puzzle input
katzuv Oct 30, 2022
426ca77
Get puzzle solution and if error has occurred print it and abort
katzuv Nov 13, 2022
308d1fd
Add constant for base website URL
katzuv Nov 14, 2022
0cda252
Add utility function that sends requests to the Advent of Code website
katzuv Nov 14, 2022
040de2f
Add class for HTTP request methods
katzuv Nov 14, 2022
4828353
Use new HTTP request function
katzuv Nov 14, 2022
e915df2
Add custom `User-Agent` header to Advent of Code HTTP requests
katzuv Nov 14, 2022
530168a
Add Beautiful Soup to Python packages requirements file
katzuv Nov 14, 2022
1774351
Add submit endpoint template
katzuv Nov 14, 2022
6ea814b
Add result parsing function
katzuv Nov 14, 2022
b3bc555
Submit solution and get result
katzuv Nov 18, 2022
5f27cf1
Print result with matching color
katzuv Nov 18, 2022
6bdb3e4
Create `__init__.py` in `commands` package
katzuv Nov 18, 2022
45cb476
Add commands to `__init__.py` in `commands` package
katzuv Nov 18, 2022
8207d97
Add commands to CLI
katzuv Nov 18, 2022
4f85447
Add Black to Python packages requirements file
katzuv Nov 18, 2022
d0ee8ac
Run `black` on whole code
katzuv Nov 18, 2022
c5da269
Move `get_default_year` to utils module
katzuv Nov 18, 2022
ba7a947
Remove now empty `defaults_and_choices` module
katzuv Nov 18, 2022
e22492b
Add command-line argument input to solution template
katzuv Nov 18, 2022
58c140c
Remove unused `session_id` parameter
katzuv Nov 18, 2022
9e8a658
Remove unused `session_id` variable
katzuv Nov 18, 2022
5cdc01a
Move CLI context constant to `cli` module
katzuv Nov 18, 2022
af8d5ca
Remove now empty `cli`\`consts` module
katzuv Nov 18, 2022
d87a8e8
Replace `click.Context(command).abort()` with `raise click.Abort()`
katzuv Nov 18, 2022
da6b4a2
Add function that runs the solution module and returns the answer
katzuv Nov 18, 2022
23bd7dd
Add function that submit the answer and returns the result
katzuv Nov 18, 2022
7b1f5db
Rename `_get_result` to `_parse_result` for better clarity
katzuv Nov 18, 2022
14d256d
Add function that styles and prints the solution result from the website
katzuv Nov 18, 2022
6cad3b7
Remove unused parameter
katzuv Nov 21, 2022
afdec7d
Remove unused function
katzuv Dec 9, 2022
8c3375e
Removed redundant file creation before writing to it
katzuv Dec 9, 2022
39984fc
Save solution file template path and not content
katzuv Dec 9, 2022
6566a9a
Copy file directly instead of reading and writing
katzuv Dec 9, 2022
962dc92
Abort if HTTP request was unsuccessful
katzuv Dec 9, 2022
e4719a1
Refactor checking whether the file is empty
katzuv Dec 9, 2022
ee77433
Fix comment
katzuv Dec 9, 2022
f3e1392
Refactor condition
katzuv Dec 9, 2022
d4b9510
Don't create solution files if they already exist
katzuv Dec 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 0 additions & 78 deletions input_getter.py

This file was deleted.

5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
requests
click
pyyaml
beautifulsoup4
black
22 changes: 22 additions & 0 deletions solution_runner/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import click

import commands


CONTEXT = {"help_option_names": ["-h", "--help"]}


@click.group(context_settings=CONTEXT)
def cli() -> click.Group:
"""
Main CLI holding all Advent of Code related commands.

For more information about Advent of Code, see https://adventofcode.com.
"""


if __name__ == "__main__":
cli.add_command(commands.setup)
cli.add_command(commands.config)
cli.add_command(commands.submit)
cli()
6 changes: 6 additions & 0 deletions solution_runner/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .config_command import command as config
from .setup_command import command as setup
from .submit_command import command as submit


__all__ = ["config", "setup", "submit"]
66 changes: 66 additions & 0 deletions solution_runner/commands/commands_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import urllib.parse
from datetime import datetime
from enum import Enum
from typing import Any

import click
import requests
import yaml

from . import consts


class PathType(Enum):
FILE = 1
DIRECTORY = 2


def get_setting(key: str) -> Any:
"""
:param key: key to retrieve its value
:return: value of `key` which is stored in the configuration file
"""
configuration_file = consts.APP_DATA_DIRECTORY / consts.CONFIGURATION_FILE_NAME
try:
configuration = yaml.safe_load(configuration_file.read_text())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are getting more than one settings per program execution, it might be better to load the configuration only once and cache the dict in memory.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache the dict in memory

How?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global variable, or defining a function performing yaml.safe_load(configuration_file.read_text()) and decorating it with @functools.cache.

except FileNotFoundError:
click.secho(
"configuration file doesn't exist. Run config command first", fg="red"
)
raise

return configuration[key]


def send_aoc_request(method, endpoint: str, payload=None) -> str:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace with a function per HTTP method. This way the GET function does not need to take an optional payload parameter.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? This is a utility function, requests.request() takes an optional payload too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requests.request() is a lower level function, the requests.get() does not receive data/payload.

"""
Send a request to Advent of Code's website and return the textual response.
:param method: method of the request
:param endpoint: endpoint to append to the base URL
:param payload: optional payload to attach to the request
:return: text content of the response
"""
if payload is None:
payload = {}
session_id = get_setting(consts.SESSION_ID)
cookies = {consts.SESSION: session_id}
url = urllib.parse.urljoin(consts.BASE_URL, endpoint)

request = requests.request(
method, url, headers=consts.USER_AGENT_HEADER, cookies=cookies, data=payload
)
if not request.ok:
click.secho(request.text, fg="red")
raise click.Abort()
return request.text


def get_default_year() -> int:
"""
:return: default year which is the current year if it's December, last year otherwise
"""
today = datetime.today()
current_year = today.year
if today.month == consts.DECEMBER:
return current_year
return current_year - 1
80 changes: 80 additions & 0 deletions solution_runner/commands/config_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from pathlib import Path
from typing import Any

import click
import yaml

from . import consts


@click.command(name="config")
@click.option(
"--root",
"root_directory",
type=consts.ROOT_DIRECTORY_TYPE,
prompt=True,
prompt_required=False,
help="root directory of Advent of Code puzzles project",
)
@click.option(
"--session-id",
prompt=True,
prompt_required=False,
hide_input=True,
help="session ID to access puzzles input",
)
def command(root_directory: str, session_id: str):
"""Set options."""
app_data_directory = click.get_app_dir(consts.APP_DATA_DIRECTORY)
configuration_file = Path(app_data_directory, consts.CONFIGURATION_FILE_NAME)
try:
# Use an empty dictionary if no actual configuration is in the configuration file.
configuration = yaml.safe_load(configuration_file.read_text()) or {}
except FileNotFoundError:
Path(app_data_directory).mkdir(exist_ok=True)
configuration_file.touch()
configuration = {}
initial_configuration = configuration.copy()

root_directory = _configure_root_directory(configuration, root_directory)
root_directory = Path(root_directory)
root_directory.mkdir(exist_ok=True)

_configure_session_id(configuration, session_id)

if configuration != initial_configuration:
configuration_file.write_text(yaml.dump(configuration))


def _configure_root_directory(
configuration: dict[str, Any], root_directory: str | None
) -> str:
"""
Edit the root directory configuration if needed.
:param configuration: current configuration
:param root_directory: root directory passed by the user, `None` if wasn't passed
:return: root directory after configuration if needed
"""
if root_directory is not None:
configuration[consts.ROOT_DIRECTORY] = root_directory
elif consts.ROOT_DIRECTORY not in configuration:
configuration[consts.ROOT_DIRECTORY] = click.prompt(
"Enter path for Advent of Code project root directory",
type=consts.ROOT_DIRECTORY_TYPE,
)
return configuration[consts.ROOT_DIRECTORY]


def _configure_session_id(configuration: dict[str, Any], session_id: str | None):
"""
Configure the session ID if needed.
:param configuration: current configuration
:param session_id: session ID passed by the user, `None` if wasn't passed
"""
if session_id is not None:
configuration[consts.SESSION_ID] = session_id
elif consts.SESSION_ID not in configuration:
configuration[consts.SESSION_ID] = click.prompt(
"Enter session ID to download input files (available in AoC website cookies)",
hide_input=True,
)
49 changes: 49 additions & 0 deletions solution_runner/commands/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import string
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

import click


FIRST_AOC_YEAR = 2015
DECEMBER = 12
ADVENT_DAYS_RANGE = click.IntRange(1, 25)
ZERO = "0"
BASE_URL = "https://adventofcode.com/"
INPUT_ENDPOINT_TEMPLATE = string.Template("/$year/day/$day/input")
SUBMIT_ENDPOINT_TEMPLATE = string.Template("/$year/day/$day/answer")
SESSION = "session"
# Requested by Advent of Code owner to help track requests.
USER_AGENT_HEADER = {"User-Agent": "AoC.CLI.katzuv"}

APP_DATA_DIRECTORY = click.get_app_dir("Advent of Code")
CONFIGURATION_FILE_NAME = Path("configuration.yaml")
ROOT_DIRECTORY_TYPE = click.Path(
file_okay=False, dir_okay=True, writable=True, readable=True, resolve_path=True
)
ROOT_DIRECTORY = "root directory"
SOLUTION_PARTS = ("p1", "p2")
SESSION_ID = "session ID"
SOLUTION_FILE_TEMPLATE_PATH = Path(Path(__file__).parent, "solution_template.py")


class Directories:
SOLUTIONS = Path("solutions")
INPUTS = Path("inputs")


class FileExtensions:
TEXT = ".txt"
PYTHON = ".py"


class HttpMethods:
GET = "GET"
POST = "POST"


US_EASTERN_TIMEZONE = ZoneInfo("US/Eastern")
AOC_UNLOCK_TIME_TEMPLATE = datetime(
year=1, month=12, day=1, hour=0, tzinfo=US_EASTERN_TIMEZONE
)
Loading