-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path__main__.py
124 lines (107 loc) · 4.41 KB
/
__main__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# =========================================================================== #
# Copyright 2023 Bruno Conde Kind #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); #
# you may not use this file except in compliance with the License. #
# You may obtain a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
# =========================================================================== #
import sys
import argparse
import fileinput
import importlib
import importlib.util
from pathlib import Path
_basemodname = Path(__file__).parent.name
def read_input(input_files: str | None):
"""Reads input file and returns a list[str], one str per non-empty line"""
lines: list[str] = []
# Use fileinput to read either from the list of files or stdin
with fileinput.input(
files=input_files if input_files else ('-',), encoding='utf-8'
) as f:
for line in f:
# Remove leading and trailing whitespaces
ln = line.strip()
# If the resulting string is not empty, add it to lines
if len(ln) > 0:
lines.append(ln)
return lines
def start():
"""
Parses command line options/arguments, calls read_input, then invokes the
function part_1(...) or part_2(...) that matches the provided day/part
"""
parser = argparse.ArgumentParser()
parser.add_argument(
'-d',
'--day',
type=int,
help='Define which day of the calendar to run',
choices=list(range(1, 26)),
required=True,
)
parser.add_argument(
'-p',
'--part',
type=int,
help='Define which part of the exercise to run',
choices=[1, 2],
required=True,
)
parser.add_argument('input_files', nargs='*', help='Input file(s)')
args = parser.parse_args()
# Read input from stdin, e.g.,
# python advent2042 --day 1 --part 1 < input/d01/part1_in00.txt
# or from provided file, e.g.,
# python advent2042 --day 1 --part 1 input/d01/part1_in00.txt
lines = read_input(args.input_files)
# The purpose of this next section is to allow execution of the project by
# running:
# python3 <basemodname> ...
# or as a module:
# python3 -m <basemodname> ...
#
# The directory for each day always has 2 digits, so days 1-9 have a
# leading 0 as padding, e.g., day 1 module: d01; day 21 module: d21;
daymodname = f'd{str(args.day).zfill(2)}'
if _basemodname in sys.modules:
daymodule = importlib.import_module(
f'{_basemodname}.{daymodname}', package=_basemodname
)
elif (spec := importlib.util.find_spec(daymodname)) is not None:
daymodule = importlib.util.module_from_spec(spec)
sys.modules[daymodname] = daymodule
if spec.loader is None:
raise ModuleNotFoundError
spec.loader.exec_module(daymodule)
else:
try:
daymodule = importlib.import_module(
f'{_basemodname}.{daymodname}', package=_basemodname
)
except ModuleNotFoundError as e:
print(
f'Module {_basemodname}.{daymodname} could not be found: {e}',
file=sys.stderr,
)
raise
# Run part 1 or 2 of the day's exercise with the lines read from input
match args.part:
case 1:
print(daymodule.part_1(lines))
case 2:
print(daymodule.part_2(lines))
case other:
# Should be unreachable since the valid choices were provided to
# argparse
raise Exception(f'Invalid part: `--part {other}`')
if __name__ == '__main__':
start()