python-ecd 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ecd/__init__.py +0 -0
- python_ecd/cli.py +189 -0
- python_ecd/templates/.gitignore.tpl +10 -0
- python_ecd/templates/README.md.tpl +40 -0
- python_ecd/templates/solution.py.tpl +18 -0
- python_ecd/utils.py +178 -0
- python_ecd/version.py +3 -0
- python_ecd-0.1.1.dist-info/METADATA +128 -0
- python_ecd-0.1.1.dist-info/RECORD +11 -0
- python_ecd-0.1.1.dist-info/WHEEL +4 -0
- python_ecd-0.1.1.dist-info/entry_points.txt +4 -0
python_ecd/__init__.py
ADDED
|
File without changes
|
python_ecd/cli.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from . import utils
|
|
6
|
+
from .version import __version__
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(name="python-ecd", help="python-ecd: CLI tool for Everybody Codes")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _display_version(value: bool) -> None:
|
|
12
|
+
"""Display the version of the application and exit."""
|
|
13
|
+
if value:
|
|
14
|
+
typer.echo(f"python-ecd {__version__}")
|
|
15
|
+
raise typer.Exit()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@app.callback()
|
|
19
|
+
def main(
|
|
20
|
+
version: bool = typer.Option(
|
|
21
|
+
None,
|
|
22
|
+
"--version",
|
|
23
|
+
"-v",
|
|
24
|
+
callback=_display_version,
|
|
25
|
+
help="Display the version and exit.",
|
|
26
|
+
is_eager=True, # Process version before other logic
|
|
27
|
+
),
|
|
28
|
+
):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command("init")
|
|
33
|
+
def init_cmd(
|
|
34
|
+
path: str = typer.Argument(None, help="Path to initialize the workspace at"),
|
|
35
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Initialize the workspace for Everybody Codes puzzles."""
|
|
38
|
+
|
|
39
|
+
# 0. Parse inputs
|
|
40
|
+
base_dir_name = "." if path is None else path
|
|
41
|
+
token = typer.prompt("Session Token", default="", show_default=False)
|
|
42
|
+
|
|
43
|
+
# 1. Create base directory
|
|
44
|
+
base_dir = utils.create_base(base_dir_name)
|
|
45
|
+
typer.echo(f"đ Created workspace at: {base_dir}")
|
|
46
|
+
|
|
47
|
+
# 2. Initialize git repository
|
|
48
|
+
git_dir = base_dir / ".git"
|
|
49
|
+
if not git_dir.exists():
|
|
50
|
+
utils.create_git(base_dir)
|
|
51
|
+
|
|
52
|
+
# 3. Create .gitignore
|
|
53
|
+
gitignore_path = base_dir / ".gitignore"
|
|
54
|
+
if gitignore_path.exists() and not force:
|
|
55
|
+
typer.echo("â ī¸ .gitignore already exists (use --force to overwrite).")
|
|
56
|
+
else:
|
|
57
|
+
utils.create_gitignore(base_dir)
|
|
58
|
+
typer.echo("đ .gitignore created")
|
|
59
|
+
|
|
60
|
+
# 4. Create README.md if requested
|
|
61
|
+
readme_path = base_dir / "README.md"
|
|
62
|
+
if readme_path.exists() and not force:
|
|
63
|
+
typer.echo("â ī¸ README.md already exists (use --force to overwrite).")
|
|
64
|
+
else:
|
|
65
|
+
utils.create_readme(base_dir, force)
|
|
66
|
+
typer.echo("đ README.md created")
|
|
67
|
+
|
|
68
|
+
# 5. Save session token if provided
|
|
69
|
+
token_path = utils.TOKEN_PATH
|
|
70
|
+
if not token:
|
|
71
|
+
typer.echo("â ī¸ No session token provided (set it later with 'ecd set-token')")
|
|
72
|
+
elif token_path.exists() and not force:
|
|
73
|
+
typer.echo("â ī¸ Session token file already exists (use --force to overwrite).")
|
|
74
|
+
else:
|
|
75
|
+
utils.set_token(token, force)
|
|
76
|
+
typer.echo(f"đ Session token saved to {token_path}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command("set-token")
|
|
80
|
+
def set_token_cmd(
|
|
81
|
+
token: str = typer.Argument(..., help="Session token to access puzzle inputs"),
|
|
82
|
+
) -> None:
|
|
83
|
+
"""Set the session token to operate with Everybody Codes webpage."""
|
|
84
|
+
utils.set_token(token)
|
|
85
|
+
typer.echo("đ Session token saved to /home/.config/ecd/token")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command("get")
|
|
89
|
+
def get_cmd(
|
|
90
|
+
quest: int = typer.Argument(..., help="Quest number (e.g. 3)"),
|
|
91
|
+
year: int = typer.Option(datetime.now().year, "--year", "-y", help="Event year"),
|
|
92
|
+
part: int = typer.Option(1, "--part", "-p", help="Puzzle part (1, 2, or 3)"),
|
|
93
|
+
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
|
|
94
|
+
) -> None:
|
|
95
|
+
"""Download input data for a given quest and create local structure."""
|
|
96
|
+
|
|
97
|
+
# 0. Locate base directory
|
|
98
|
+
base_dir = utils.find_base()
|
|
99
|
+
|
|
100
|
+
# 1. Ensure session token exists
|
|
101
|
+
if not utils.get_token():
|
|
102
|
+
typer.echo(
|
|
103
|
+
"â Error: Session token not found in .env. Use 'ecd set-token' to set it."
|
|
104
|
+
)
|
|
105
|
+
raise typer.Exit(1)
|
|
106
|
+
|
|
107
|
+
# 2. Download the input for the given part
|
|
108
|
+
try:
|
|
109
|
+
input_text = utils.download_input(year, quest, part)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
typer.echo(f"â Failed to fetch input: {e}")
|
|
112
|
+
typer.echo("âšī¸ Maybe ")
|
|
113
|
+
raise typer.Exit(1)
|
|
114
|
+
|
|
115
|
+
# 3. Prepare directory structure
|
|
116
|
+
quest_dir = utils.create_quest_dir(base_dir, year, quest)
|
|
117
|
+
|
|
118
|
+
# 4. Create solution.py if missing
|
|
119
|
+
solution_file = quest_dir / "solution.py"
|
|
120
|
+
if solution_file.exists() and not force:
|
|
121
|
+
typer.echo("â ī¸ solution.py already exists (use --force to overwrite).")
|
|
122
|
+
else:
|
|
123
|
+
utils.create_solution(quest_dir, force)
|
|
124
|
+
typer.echo("đ§Š Created solution.py")
|
|
125
|
+
|
|
126
|
+
# 5. Save input
|
|
127
|
+
input_file = quest_dir / f"input/input_p{part}.txt"
|
|
128
|
+
if input_file.exists() and not force:
|
|
129
|
+
typer.echo("â ī¸ Input file already exists (use --force to overwrite).")
|
|
130
|
+
else:
|
|
131
|
+
input_file.write_text(input_text, encoding="utf-8")
|
|
132
|
+
typer.echo(f"đĨ Saved input for quest {quest:02d} part {part}.")
|
|
133
|
+
|
|
134
|
+
# 6. Ensure empty test file exists
|
|
135
|
+
test_file = quest_dir / f"test/test_p{part}.txt"
|
|
136
|
+
if test_file.exists() and not force:
|
|
137
|
+
typer.echo("â ī¸ Test file already exists (use --force to overwrite).")
|
|
138
|
+
else:
|
|
139
|
+
test_file.touch(exist_ok=True)
|
|
140
|
+
|
|
141
|
+
typer.echo(
|
|
142
|
+
f"â
Quest {quest:02d} (Part {part}) ready at {solution_file.relative_to(base_dir)}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command("run")
|
|
147
|
+
def run_cmd(
|
|
148
|
+
quest: int = typer.Argument(..., help="Quest number (e.g. 3)"),
|
|
149
|
+
year: int = typer.Option(datetime.now().year, "--year", "-y", help="Event year"),
|
|
150
|
+
part: int = typer.Option(1, "--part", "-p", help="Part number to execute"),
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Execute the solution for a given quest and part using input data."""
|
|
153
|
+
|
|
154
|
+
# 0. Locate base directory
|
|
155
|
+
base_dir = utils.find_base()
|
|
156
|
+
|
|
157
|
+
# 1. Execute the solution
|
|
158
|
+
try:
|
|
159
|
+
result = utils.execute_part(base_dir, quest, year, part, mode="input")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
typer.echo(f"â Failed to execute Quest {quest} Part {part}: {e}")
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
|
|
164
|
+
typer.echo(f"â
Result for Quest {quest} Part {part}:\n{result}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@app.command("test")
|
|
168
|
+
def test_cmd(
|
|
169
|
+
quest: int = typer.Argument(..., help="Quest number (e.g. 3)"),
|
|
170
|
+
year: int = typer.Option(datetime.now().year, "--year", "-y", help="Event year"),
|
|
171
|
+
part: int = typer.Option(1, "--part", "-p", help="Part number to test"),
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Run the solution for a given quest and part using test data."""
|
|
174
|
+
|
|
175
|
+
# 0. Locate base directory
|
|
176
|
+
base_dir = utils.find_base()
|
|
177
|
+
|
|
178
|
+
# 1. Execute the solution in test mode
|
|
179
|
+
try:
|
|
180
|
+
result = utils.execute_part(base_dir, quest, year, part, mode="test")
|
|
181
|
+
except Exception as e:
|
|
182
|
+
typer.echo(f"â Failed to run test for Quest {quest} Part {part}: {e}")
|
|
183
|
+
raise typer.Exit(1)
|
|
184
|
+
|
|
185
|
+
typer.echo(f"đ§Ē Test result for Quest {quest} Part {part}:\n{result}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
app()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# đ§Š Everybody Codes Solutions
|
|
2
|
+
|
|
3
|
+
My solutions to the [Everybody Codes](https://everybody.codes/) puzzles (managed by the [python-ecd](https://github.com/pablofueros/python-ecd) library).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## đ Project Structure
|
|
8
|
+
|
|
9
|
+
Each quest is stored under `events/<year>/quest_<id>/` and contains:
|
|
10
|
+
|
|
11
|
+
| File / Folder | Description |
|
|
12
|
+
|----------------|-------------|
|
|
13
|
+
| `solution.py` | Your Python solution with `part_1`, `part_2`, and `part_3` functions. |
|
|
14
|
+
| `input/` | Puzzle inputs (`input_p1.txt`, `input_p2.txt`, âĻ) fetched automatically. |
|
|
15
|
+
| `test/` | Optional test files (`test_p1.txt`, âĻ) for local validation. |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## â
Completed Quests
|
|
20
|
+
|
|
21
|
+
| Year | Quest | Part 1 | Part 2 | Part 3 |
|
|
22
|
+
|------|--------|--------|--------|--------|
|
|
23
|
+
| yyyy | n | â
| âŦ | âŦ |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## đ Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Initialize your workspace
|
|
31
|
+
ecd init
|
|
32
|
+
|
|
33
|
+
# Fetch a puzzle input ()
|
|
34
|
+
ecd get 3 # Quest 3 of the current year
|
|
35
|
+
|
|
36
|
+
# Run your test cases
|
|
37
|
+
ecd test 3 --part 1
|
|
38
|
+
|
|
39
|
+
# Execute your actual input
|
|
40
|
+
ecd run 3 --part 1
|
python_ecd/utils.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import importlib.util
|
|
3
|
+
import io
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ecd import get_inputs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _read_template(template_name: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Read a template file from the templates directory.
|
|
13
|
+
"""
|
|
14
|
+
templates_dir = Path(__file__).parent / "templates"
|
|
15
|
+
template_path = templates_dir / template_name
|
|
16
|
+
text = template_path.read_text(encoding="utf-8")
|
|
17
|
+
return text
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def create_base(base_dir_name: str) -> Path:
|
|
21
|
+
"""
|
|
22
|
+
Create the base directory for the project, including subfolders.
|
|
23
|
+
"""
|
|
24
|
+
base_dir = Path(base_dir_name).resolve()
|
|
25
|
+
(base_dir / "events").mkdir(parents=True, exist_ok=True)
|
|
26
|
+
return base_dir
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def find_base() -> Path:
|
|
30
|
+
"""
|
|
31
|
+
Locate the base directory by searching upwards from the current directory.
|
|
32
|
+
"""
|
|
33
|
+
current_dir = Path.cwd()
|
|
34
|
+
for parent in [current_dir] + list(current_dir.parents):
|
|
35
|
+
if (parent / "events").exists():
|
|
36
|
+
return parent
|
|
37
|
+
raise FileNotFoundError("Base directory not found. Please run 'ecd init'.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def create_quest_dir(base_dir: Path, year: int, quest: int) -> Path:
|
|
41
|
+
"""
|
|
42
|
+
Create directory structure for a given quest/year.
|
|
43
|
+
"""
|
|
44
|
+
quest_dir = base_dir / "events" / str(year) / f"quest_{quest:02d}"
|
|
45
|
+
quest_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
(quest_dir / "input").mkdir(exist_ok=True)
|
|
47
|
+
(quest_dir / "test").mkdir(exist_ok=True)
|
|
48
|
+
return quest_dir
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def create_readme(base_dir: Path, force: bool) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Create a simple README.md in the base directory.
|
|
54
|
+
"""
|
|
55
|
+
readme_path = base_dir / "README.md"
|
|
56
|
+
if not readme_path.exists() or force:
|
|
57
|
+
content = _read_template("README.md.tpl")
|
|
58
|
+
readme_path.write_text(content, encoding="utf-8")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def create_git(base_dir: Path) -> None:
|
|
62
|
+
"""
|
|
63
|
+
Initialize a git repository in the base directory.
|
|
64
|
+
"""
|
|
65
|
+
git_dir = base_dir / ".git"
|
|
66
|
+
if git_dir.exists():
|
|
67
|
+
return # Git already initialized
|
|
68
|
+
subprocess.run(
|
|
69
|
+
["git", "init"],
|
|
70
|
+
cwd=str(base_dir),
|
|
71
|
+
check=True,
|
|
72
|
+
stdout=subprocess.DEVNULL,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def create_gitignore(base_dir: Path) -> None:
|
|
77
|
+
gitignore_path = base_dir / ".gitignore"
|
|
78
|
+
if not gitignore_path.exists():
|
|
79
|
+
content = _read_template(".gitignore.tpl")
|
|
80
|
+
gitignore_path.write_text(content, encoding="utf-8")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def create_solution(quest_dir: Path, force: bool) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Create a solution.py file in the quest directory.
|
|
86
|
+
"""
|
|
87
|
+
solution_path = quest_dir / "solution.py"
|
|
88
|
+
if not solution_path.exists() or force:
|
|
89
|
+
content = _read_template("solution.py.tpl")
|
|
90
|
+
solution_path.write_text(content, encoding="utf-8")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
TOKEN_PATH = Path.home() / ".config" / "ecd" / "token"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def set_token(token: str, force: bool = False) -> None:
|
|
97
|
+
"""Create ~/.config/ecd/token with the given token."""
|
|
98
|
+
config_dir = TOKEN_PATH.parent
|
|
99
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
100
|
+
TOKEN_PATH.write_text(token.strip())
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_token() -> str | None:
|
|
104
|
+
"""Read the token if it exists, else return None."""
|
|
105
|
+
if not TOKEN_PATH.exists():
|
|
106
|
+
return None
|
|
107
|
+
return TOKEN_PATH.read_text().strip()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def download_input(year: int, quest: int, part: int) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Fetch the input text for a specific quest and part.
|
|
113
|
+
Raises if part not available.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
with contextlib.redirect_stderr(io.StringIO()):
|
|
117
|
+
data = get_inputs(quest=quest, event=year)
|
|
118
|
+
|
|
119
|
+
key = str(part)
|
|
120
|
+
if key not in data:
|
|
121
|
+
raise ValueError(f"Part {part} not unlocked or not available.")
|
|
122
|
+
|
|
123
|
+
return data[key]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def execute_part(
|
|
127
|
+
base_dir: Path,
|
|
128
|
+
quest: int,
|
|
129
|
+
year: int,
|
|
130
|
+
part: int,
|
|
131
|
+
mode: str = "input",
|
|
132
|
+
) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Execute a given part of a quest solution with either input or test data.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
base_dir: Base directory
|
|
138
|
+
part: Part number (1, 2, or 3)
|
|
139
|
+
mode: Either "input" or "test"
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The string result produced by part_{part}(data).
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
quest_dir = base_dir / "events" / str(year) / f"quest_{quest:02d}"
|
|
146
|
+
solution_path = quest_dir / "solution.py"
|
|
147
|
+
data_path = quest_dir / mode / f"{mode}_p{part}.txt"
|
|
148
|
+
|
|
149
|
+
if not solution_path.exists():
|
|
150
|
+
raise FileNotFoundError(
|
|
151
|
+
f"Missing solution file: {solution_path.relative_to(base_dir)}"
|
|
152
|
+
)
|
|
153
|
+
if not data_path.exists():
|
|
154
|
+
raise FileNotFoundError(f"Missing data file: {data_path.relative_to(base_dir)}")
|
|
155
|
+
|
|
156
|
+
# Load data
|
|
157
|
+
data = data_path.read_text(encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
# Dynamic import
|
|
160
|
+
spec = importlib.util.spec_from_file_location("solution", solution_path)
|
|
161
|
+
if spec is None or spec.loader is None:
|
|
162
|
+
raise ImportError(
|
|
163
|
+
f"Could not load module from {solution_path.relative_to(base_dir)}"
|
|
164
|
+
)
|
|
165
|
+
module = importlib.util.module_from_spec(spec)
|
|
166
|
+
spec.loader.exec_module(module)
|
|
167
|
+
|
|
168
|
+
func_name = f"part_{part}"
|
|
169
|
+
func = getattr(module, func_name, None)
|
|
170
|
+
if not callable(func):
|
|
171
|
+
raise AttributeError(f"No function '{func_name}' defined in solution.py")
|
|
172
|
+
|
|
173
|
+
# Run the function
|
|
174
|
+
result = func(data)
|
|
175
|
+
if result is None:
|
|
176
|
+
raise ValueError(f"{func_name} returned None.")
|
|
177
|
+
|
|
178
|
+
return str(result)
|
python_ecd/version.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: python-ecd
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Pablo Garcia
|
|
6
|
+
Author-email: Pablo Garcia <pablofueros@gmail.com>
|
|
7
|
+
Requires-Dist: everybody-codes-data==0.2
|
|
8
|
+
Requires-Dist: typer>=0.20.0
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
<div align="center">
|
|
13
|
+
|
|
14
|
+
<img src="https://raw.githubusercontent.com/pablofueros/python-ecd/main/assets/banner.png" alt="python-ecd logo" width="600"/>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### **⨠A Python CLI tool for managing Everybody Codes puzzles â¨**
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
## đ Features
|
|
25
|
+
|
|
26
|
+
- Initialize workspace for [Everybody Codes](https://everybody.codes) puzzles
|
|
27
|
+
- Download puzzle inputs automatically
|
|
28
|
+
- Manage session tokens securely
|
|
29
|
+
- Run solutions with both test and real input data
|
|
30
|
+
- Automatic project structure creation
|
|
31
|
+
|
|
32
|
+
## đĻ Installation
|
|
33
|
+
|
|
34
|
+
The package can be installed using [uv](https://docs.astral.sh/uv/#installation), which is recommended for better dependency management and faster installations:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Install it as a system tool
|
|
38
|
+
uv tool install python-ecd
|
|
39
|
+
|
|
40
|
+
# Otherwise use:
|
|
41
|
+
uvx python-ecd
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The tool will be available as both 'python-ecd' and 'ecd'
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## đģ Usage
|
|
48
|
+
|
|
49
|
+
### Initialize a Workspace
|
|
50
|
+
|
|
51
|
+
Create a new workspace for your puzzles solutions:
|
|
52
|
+
|
|
53
|
+
It can be done in the current directory:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
ecd init
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or in a specified path:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ecd init everybody-codes-solutions
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Note that if the directory does not exist, it will be created.
|
|
66
|
+
|
|
67
|
+
### Set Session Token
|
|
68
|
+
|
|
69
|
+
Configure your session token for accessing puzzle inputs:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ecd set-token <TOKEN>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Note that is not necessary if you set it during initialization.
|
|
76
|
+
|
|
77
|
+
### Download Puzzle Input
|
|
78
|
+
|
|
79
|
+
Get the input for a specific puzzle:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ecd get <QUEST_NUMBER> [OPTIONS]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Options:
|
|
86
|
+
- `--year`, `-y`: Event year (default: actual)
|
|
87
|
+
- `--part`, `-p`: Puzzle part (default: 1)
|
|
88
|
+
- `--force`, `-f`: Overwrite existing files
|
|
89
|
+
|
|
90
|
+
### Run Solutions
|
|
91
|
+
|
|
92
|
+
Execute your solution for a specific puzzle:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
ecd run <QUEST_NUMBER> [OPTIONS]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
- `--year`: Event year (default: actual)
|
|
100
|
+
- `--part`: Part number to execute (default: 1)
|
|
101
|
+
|
|
102
|
+
### Test Solutions
|
|
103
|
+
|
|
104
|
+
Run your solution using test data:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
ecd test <QUEST_NUMBER> [OPTIONS]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Options:
|
|
111
|
+
- `--year`: Event year (default: actual)
|
|
112
|
+
- `--part`: Part number to test (default: 1)
|
|
113
|
+
|
|
114
|
+
### Display Version
|
|
115
|
+
|
|
116
|
+
Show the current version of the tool:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
ecd --version
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## ÂŠī¸ License
|
|
123
|
+
|
|
124
|
+
[MIT License](LICENSE)
|
|
125
|
+
|
|
126
|
+
## đ¤ Contributing
|
|
127
|
+
|
|
128
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
python_ecd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
python_ecd/cli.py,sha256=afZomWPhSZKi28PAhj2qY-TZWJ4GI5R7ZhbWB92pMwc,6378
|
|
3
|
+
python_ecd/templates/.gitignore.tpl,sha256=y_SVda3Ui7hqPOLVZ56uXZUdV7W2O5rP2zqJ00fMq5E,108
|
|
4
|
+
python_ecd/templates/README.md.tpl,sha256=IIegKMG7sarBFU2b_2BvbQJ81kml4DBfGSV-_7bfIUg,989
|
|
5
|
+
python_ecd/templates/solution.py.tpl,sha256=1VB0m1VJ5I1aXoGnCASU0ebG2hkW7UPBk4r1aUFO8Ao,220
|
|
6
|
+
python_ecd/utils.py,sha256=c8MUh2T52n0jFlv2bjkrgHyemdKS-Uuw0ysTGKFHp1c,5148
|
|
7
|
+
python_ecd/version.py,sha256=XfNNhTw26h98ofJAEHXMEso8xh2OqwH7tu-6Z1kjmSs,84
|
|
8
|
+
python_ecd-0.1.1.dist-info/WHEEL,sha256=5w2T7AS2mz1-rW9CNagNYWRCaB0iQqBMYLwKdlgiR4Q,78
|
|
9
|
+
python_ecd-0.1.1.dist-info/entry_points.txt,sha256=K_TJwtTcFG_QZTb0wkM-Le2ybbo-u3BXcaCWQTP0bZ0,76
|
|
10
|
+
python_ecd-0.1.1.dist-info/METADATA,sha256=sPMLb4rXxiYN28D_9jrTQ3Le5i2kB_U4K5Mx64s9cag,2505
|
|
11
|
+
python_ecd-0.1.1.dist-info/RECORD,,
|