From a3de698aa6b7e15e9d0974d32dc566676383bd28 Mon Sep 17 00:00:00 2001 From: Tom van der Lee Date: Mon, 28 Nov 2022 13:52:27 +0100 Subject: Initial code --- .gitignore | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ aoc/__init__.py | 27 +++++++ aoc/__main__.py | 47 +++++++++++ aoc/test_init.py | 2 + aoc/utils.py | 5 ++ conftest.py | 32 ++++++++ requirements.txt | 2 + 7 files changed, 349 insertions(+) create mode 100644 .gitignore create mode 100644 aoc/__init__.py create mode 100644 aoc/__main__.py create mode 100644 aoc/test_init.py create mode 100644 aoc/utils.py create mode 100644 conftest.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f402d03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,234 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm+all +# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm+all + +### PyCharm+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm+all Patch ### +# Ignores the whole .idea folder and all .iml files +# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 + +.idea/ + +# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 + +*.iml +modules.xml +.idea/misc.xml +*.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# profiling data +.prof + +# End of https://www.toptal.com/developers/gitignore/api/python,pycharm+all diff --git a/aoc/__init__.py b/aoc/__init__.py new file mode 100644 index 0000000..b13489c --- /dev/null +++ b/aoc/__init__.py @@ -0,0 +1,27 @@ +import os +from abc import ABC +from typing import Generator, Any, Iterator + + +class BaseAssignment(ABC): + example_result = NotImplemented + def __init__(self, path): + self.path = path + + def __str__(self): + return f'{self.__module__}.{self.__class__.__name__}' + + def parse_item(self, item: str) -> Any: + return item + + def read_input(self, example = False) -> Generator: + file = f'{self.path}/input.txt' + if example or not os.path.isfile(file): + file = f'{self.path}/example.txt' + + with open(file, 'r') as input_file: + for line in input_file.readlines(): + yield self.parse_item(line.strip()) + + def run(self, input: Iterator) -> Any: + raise NotImplementedError('Please implement run') \ No newline at end of file diff --git a/aoc/__main__.py b/aoc/__main__.py new file mode 100644 index 0000000..25a7026 --- /dev/null +++ b/aoc/__main__.py @@ -0,0 +1,47 @@ +import argparse +import importlib +import os +from time import perf_counter +from typing import List + + +def day(assignment_part: str) -> str: + return { + '1': 'One', + '2': 'Two', + }[assignment_part] + +def kwargs(kwarg: str) -> List: + return kwarg.split('=') + +parser = argparse.ArgumentParser(description='Advent of Code') + +parser.add_argument('day', type=str, help='Assignment day') +parser.add_argument( + '-p', '--part', type=day, nargs='?', default='1', + help='Assingment part. Defaults to one.' +) +parser.add_argument('--example', default=False, action='store_true') +parser.add_argument('kwargs', type=kwargs, nargs='*') + + +if __name__ == '__main__': + args = parser.parse_args() + assignment_day = importlib.import_module(args.day) + + Assignment = getattr(assignment_day, f'Assignment{args.part}') + assignment = Assignment( + path=os.path.dirname(assignment_day.__file__), + **dict(args.kwargs) + ) + + start = perf_counter() + print( + assignment.run( + input=assignment.read_input(example=args.example), + ) + ) + end = perf_counter() + delta = end - start + print() + print(f'{(delta if delta > 1 else delta * 1000):.3f}{"s" if delta > 1 else "ms"}') diff --git a/aoc/test_init.py b/aoc/test_init.py new file mode 100644 index 0000000..8524086 --- /dev/null +++ b/aoc/test_init.py @@ -0,0 +1,2 @@ +def test_assingment_examples(assignment): + assert assignment.run(input=assignment.read_input(example=True)) == assignment.example_result diff --git a/aoc/utils.py b/aoc/utils.py new file mode 100644 index 0000000..6d794f4 --- /dev/null +++ b/aoc/utils.py @@ -0,0 +1,5 @@ +from typing import Callable + + +def bold(item: str, condition: Callable[[], bool]): + return f'\033[36m{item}\033[0m' if condition() else item diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..24884e2 --- /dev/null +++ b/conftest.py @@ -0,0 +1,32 @@ +import importlib +import os +from pkgutil import walk_packages + +from _pytest.python import Metafunc + +from aoc.__main__ import day + +dir_path = os.path.dirname(os.path.realpath(__file__)) + +def pytest_generate_tests(metafunc: Metafunc): + if 'assignment' in metafunc.fixturenames: + packages = [ + importlib.import_module(package.name) + for package in walk_packages([dir_path]) + ] + + assignments = [ + (getattr(package, f'Assignment{day(part)}', None), package) + for package in packages + for part in ['1', '2'] + ] + + metafunc.parametrize( + argnames=f'assignment', + argvalues=[ + Assignment(path=package.__path__[0]) + for (Assignment, package) in assignments + if Assignment is not None and hasattr(package, '__path__') and Assignment.example_result != NotImplemented + ], + ids=lambda assignment: str(assignment) + ) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..49ec960 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pytest +coverage -- cgit v1.2.3