heroalgorithm 0.1.0__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.
- heroalgorithm/__init__.py +6 -0
- heroalgorithm/assignments/PS-1/README.md +11 -0
- heroalgorithm/assignments/PS-1/dataset/data.csv +7 -0
- heroalgorithm/assignments/PS-1/notebook.ipynb +32 -0
- heroalgorithm/assignments/PS-1/solution.py +28 -0
- heroalgorithm/assignments/PS-2/README.md +5 -0
- heroalgorithm/assignments/PS-2/dataset/housing.csv +5 -0
- heroalgorithm/assignments/PS-2/notebook.ipynb +32 -0
- heroalgorithm/assignments/PS-2/solution.py +10 -0
- heroalgorithm/assignments/PS-3/README.md +5 -0
- heroalgorithm/assignments/PS-3/dataset/iris.csv +4 -0
- heroalgorithm/assignments/PS-3/notebook.ipynb +32 -0
- heroalgorithm/assignments/PS-3/solution.py +10 -0
- heroalgorithm/assignments/README.md +28 -0
- heroalgorithm/cli.py +293 -0
- heroalgorithm/config.py +132 -0
- heroalgorithm/downloader.py +270 -0
- heroalgorithm-0.1.0.dist-info/METADATA +165 -0
- heroalgorithm-0.1.0.dist-info/RECORD +23 -0
- heroalgorithm-0.1.0.dist-info/WHEEL +5 -0
- heroalgorithm-0.1.0.dist-info/entry_points.txt +3 -0
- heroalgorithm-0.1.0.dist-info/licenses/LICENSE +21 -0
- heroalgorithm-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Problem Sheet 1 (PS-1)
|
|
2
|
+
|
|
3
|
+
## Objectives
|
|
4
|
+
- Load and preprocess tabular dataset from `dataset/data.csv`.
|
|
5
|
+
- Implement simple classification model.
|
|
6
|
+
- Analyze feature correlation and accuracy metrics.
|
|
7
|
+
|
|
8
|
+
## Files
|
|
9
|
+
- `dataset/data.csv`: Input training data.
|
|
10
|
+
- `notebook.ipynb`: Interactive analysis & visualization.
|
|
11
|
+
- `solution.py`: Standalone Python implementation script.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# PS-1: Linear Classification\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"Jupyter Notebook for AIML Lab.\n"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"cell_type": "code",
|
|
14
|
+
"execution_count": null,
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"outputs": [],
|
|
17
|
+
"source": [
|
|
18
|
+
"import pandas as pd\n",
|
|
19
|
+
"import numpy as np\n",
|
|
20
|
+
"df = pd.read_csv(\"dataset/data.csv\")\n",
|
|
21
|
+
"df.head()\n"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"metadata": {
|
|
26
|
+
"language_info": {
|
|
27
|
+
"name": "python"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"nbformat": 4,
|
|
31
|
+
"nbformat_minor": 5
|
|
32
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIML Problem Sheet 1 (PS-1)
|
|
3
|
+
Linear Regression / Classification Implementation
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_dataset():
|
|
11
|
+
data_path = Path(__file__).parent / "dataset" / "data.csv"
|
|
12
|
+
if not data_path.exists():
|
|
13
|
+
raise FileNotFoundError(f"Dataset not found at: {data_path}")
|
|
14
|
+
df = pd.read_csv(data_path)
|
|
15
|
+
print(f"Loaded dataset with {len(df)} rows:")
|
|
16
|
+
print(df.head())
|
|
17
|
+
return df
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
print("Executing PS-1 Solution...")
|
|
22
|
+
df = load_dataset()
|
|
23
|
+
print("Features:", [c for c in df.columns if c != "target"])
|
|
24
|
+
print("Target distribution:", df["target"].value_counts().to_dict())
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# PS-2: Decision Trees & Regression\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"Jupyter Notebook for AIML Lab.\n"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"cell_type": "code",
|
|
14
|
+
"execution_count": null,
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"outputs": [],
|
|
17
|
+
"source": [
|
|
18
|
+
"import pandas as pd\n",
|
|
19
|
+
"import numpy as np\n",
|
|
20
|
+
"df = pd.read_csv(\"dataset/housing.csv\")\n",
|
|
21
|
+
"df.head()\n"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"metadata": {
|
|
26
|
+
"language_info": {
|
|
27
|
+
"name": "python"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"nbformat": 4,
|
|
31
|
+
"nbformat_minor": 5
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""AIML Problem Sheet 2 (PS-2): Decision Trees & Regression"""
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
df = pd.read_csv(Path(__file__).parent / "dataset" / "housing.csv")
|
|
7
|
+
print("PS-2 Housing dataset loaded:", df.shape)
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# PS-3: Clustering & PCA\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"Jupyter Notebook for AIML Lab.\n"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"cell_type": "code",
|
|
14
|
+
"execution_count": null,
|
|
15
|
+
"metadata": {},
|
|
16
|
+
"outputs": [],
|
|
17
|
+
"source": [
|
|
18
|
+
"import pandas as pd\n",
|
|
19
|
+
"import numpy as np\n",
|
|
20
|
+
"df = pd.read_csv(\"dataset/iris.csv\")\n",
|
|
21
|
+
"df.head()\n"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"metadata": {
|
|
26
|
+
"language_info": {
|
|
27
|
+
"name": "python"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"nbformat": 4,
|
|
31
|
+
"nbformat_minor": 5
|
|
32
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""AIML Problem Sheet 3 (PS-3): Neural Networks / Clustering"""
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
df = pd.read_csv(Path(__file__).parent / "dataset" / "iris.csv")
|
|
7
|
+
print("PS-3 Iris dataset loaded:", df.shape)
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
main()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ðĪ AI / ML Lab Assignments Repository
|
|
2
|
+
|
|
3
|
+
This repository hosts all AI & Machine Learning lab problem sheets and code.
|
|
4
|
+
|
|
5
|
+
Each problem sheet follows a consistent structure:
|
|
6
|
+
```text
|
|
7
|
+
PS-X/
|
|
8
|
+
âââ dataset/
|
|
9
|
+
â âââ dataset.csv
|
|
10
|
+
âââ psX_notebook.ipynb
|
|
11
|
+
âââ psX_solution.py
|
|
12
|
+
âââ README.md
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Download with AssignVault
|
|
16
|
+
Students can download and organize everything locally with a single terminal command:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Download all Problem Sheets
|
|
20
|
+
assignvault download
|
|
21
|
+
|
|
22
|
+
# 2. Or download a specific Problem Sheet (e.g. PS-1)
|
|
23
|
+
assignvault download ps-1
|
|
24
|
+
# or: assignvault download 1
|
|
25
|
+
|
|
26
|
+
# 3. List all available problem sheets
|
|
27
|
+
assignvault list
|
|
28
|
+
```
|
heroalgorithm/cli.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI interface for HeroAlgorithm.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from heroalgorithm import __version__
|
|
13
|
+
from heroalgorithm.config import (
|
|
14
|
+
get_branch,
|
|
15
|
+
get_destination,
|
|
16
|
+
get_repo,
|
|
17
|
+
get_token,
|
|
18
|
+
load_config,
|
|
19
|
+
reset_config,
|
|
20
|
+
set_branch,
|
|
21
|
+
set_destination,
|
|
22
|
+
set_repo,
|
|
23
|
+
)
|
|
24
|
+
from heroalgorithm.downloader import (
|
|
25
|
+
HeroAlgorithmError,
|
|
26
|
+
extract_archive,
|
|
27
|
+
extract_bundled,
|
|
28
|
+
fetch_archive,
|
|
29
|
+
inspect_archive,
|
|
30
|
+
parse_repo_string,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Reconfigure console streams on Windows to prevent cp1252 charmap errors
|
|
34
|
+
if sys.platform == "win32":
|
|
35
|
+
try:
|
|
36
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
37
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
38
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
39
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_install(
|
|
45
|
+
destination: Path,
|
|
46
|
+
subject: Optional[str] = None,
|
|
47
|
+
repo_str: Optional[str] = None,
|
|
48
|
+
branch: Optional[str] = None,
|
|
49
|
+
token: Optional[str] = None,
|
|
50
|
+
force: bool = True,
|
|
51
|
+
quiet: bool = False,
|
|
52
|
+
) -> int:
|
|
53
|
+
"""
|
|
54
|
+
Executes assignment extraction. Keeps verbose installation logs hidden.
|
|
55
|
+
"""
|
|
56
|
+
target_info = f" ({subject})" if subject else ""
|
|
57
|
+
if not quiet:
|
|
58
|
+
print(f"ðĶ HeroAlgorithm: Organizing AIML Problem Sheets{target_info} into:\n {destination.resolve()}")
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# If a remote GitHub repository is specified, fetch from GitHub
|
|
62
|
+
if repo_str:
|
|
63
|
+
owner, repo = parse_repo_string(repo_str)
|
|
64
|
+
branch = branch or get_branch()
|
|
65
|
+
archive_data = fetch_archive(
|
|
66
|
+
owner=owner,
|
|
67
|
+
repo=repo,
|
|
68
|
+
branch=branch,
|
|
69
|
+
token=token,
|
|
70
|
+
silent=True,
|
|
71
|
+
)
|
|
72
|
+
extracted = extract_archive(
|
|
73
|
+
archive_data=archive_data,
|
|
74
|
+
destination=destination,
|
|
75
|
+
subject_filter=subject,
|
|
76
|
+
overwrite=force,
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
# Extract bundled problem sheets directly from package
|
|
80
|
+
extracted = extract_bundled(
|
|
81
|
+
destination=destination,
|
|
82
|
+
subject_filter=subject,
|
|
83
|
+
overwrite=force,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
except HeroAlgorithmError as e:
|
|
87
|
+
print(f"â Error: {e}")
|
|
88
|
+
return 1
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(f"â Unexpected error: {e}")
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
if not extracted:
|
|
94
|
+
if subject:
|
|
95
|
+
print(f"â ïļ No problem sheets found matching '{subject}'.")
|
|
96
|
+
else:
|
|
97
|
+
print("â ïļ No files were extracted.")
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
if not quiet:
|
|
101
|
+
print(f"\nâ
Ready! {len(extracted)} files organized at:\n {destination.resolve()}\n")
|
|
102
|
+
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def handle_download(args: argparse.Namespace) -> int:
|
|
107
|
+
destination = get_destination(args.output)
|
|
108
|
+
repo = get_repo(args.repo)
|
|
109
|
+
branch = get_branch(args.branch)
|
|
110
|
+
token = get_token(args.token)
|
|
111
|
+
return run_install(
|
|
112
|
+
destination=destination,
|
|
113
|
+
subject=args.subject,
|
|
114
|
+
repo_str=repo,
|
|
115
|
+
branch=branch,
|
|
116
|
+
token=token,
|
|
117
|
+
force=args.force,
|
|
118
|
+
quiet=args.quiet,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def handle_list(args: argparse.Namespace) -> int:
|
|
123
|
+
repo_str = get_repo(args.repo)
|
|
124
|
+
if not repo_str:
|
|
125
|
+
# List bundled sheets
|
|
126
|
+
from heroalgorithm.downloader import BUNDLED_DIR
|
|
127
|
+
print(f"ð Available Bundled Problem Sheets:\n")
|
|
128
|
+
if BUNDLED_DIR.exists():
|
|
129
|
+
for folder in sorted(BUNDLED_DIR.iterdir()):
|
|
130
|
+
if folder.is_dir() and not folder.name.startswith("."):
|
|
131
|
+
files = [f.relative_to(folder).as_posix() for f in folder.rglob("*") if f.is_file()]
|
|
132
|
+
print(f"ð {folder.name} ({len(files)} files)")
|
|
133
|
+
for f in files:
|
|
134
|
+
print(f" âââ {f}")
|
|
135
|
+
print("\nðĄ Tip: Run 'heroalgorithm <sheet-number>' to unpack a specific sheet.")
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
# Remote GitHub repo list
|
|
139
|
+
try:
|
|
140
|
+
owner, repo = parse_repo_string(repo_str)
|
|
141
|
+
branch = get_branch(args.branch)
|
|
142
|
+
token = get_token(args.token)
|
|
143
|
+
print(f"ð Inspecting remote catalogue in {owner}/{repo} (branch: {branch})...\n")
|
|
144
|
+
archive_data = fetch_archive(owner=owner, repo=repo, branch=branch, token=token, silent=True)
|
|
145
|
+
structure = inspect_archive(archive_data)
|
|
146
|
+
print("ð Available Problem Sheets & Lab Files:")
|
|
147
|
+
for subject, files in sorted(structure.items()):
|
|
148
|
+
print(f"\nð {subject} ({len(files)} files)")
|
|
149
|
+
for f in files[:8]:
|
|
150
|
+
print(f" âââ {f}")
|
|
151
|
+
if len(files) > 8:
|
|
152
|
+
print(f" âââ ... and {len(files) - 8} more files")
|
|
153
|
+
return 0
|
|
154
|
+
except HeroAlgorithmError as e:
|
|
155
|
+
print(f"â Error: {e}")
|
|
156
|
+
return 1
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def handle_config(args: argparse.Namespace) -> int:
|
|
160
|
+
if args.reset:
|
|
161
|
+
reset_config()
|
|
162
|
+
print("â
Configuration reset to defaults.")
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
if args.set_dest:
|
|
166
|
+
set_destination(args.set_dest)
|
|
167
|
+
print(f"â
Default destination set to: '{get_destination()}'")
|
|
168
|
+
|
|
169
|
+
if args.set_repo:
|
|
170
|
+
try:
|
|
171
|
+
parse_repo_string(args.set_repo)
|
|
172
|
+
set_repo(args.set_repo)
|
|
173
|
+
print(f"â
Default GitHub repo set to: '{args.set_repo}'")
|
|
174
|
+
except HeroAlgorithmError as e:
|
|
175
|
+
print(f"â {e}")
|
|
176
|
+
return 1
|
|
177
|
+
|
|
178
|
+
if args.set_branch:
|
|
179
|
+
set_branch(args.set_branch)
|
|
180
|
+
print(f"â
Default branch set to: '{args.set_branch}'")
|
|
181
|
+
|
|
182
|
+
cfg = load_config()
|
|
183
|
+
print("\nâïļ HeroAlgorithm Configuration:")
|
|
184
|
+
print(f" âĒ Destination: {get_destination()} {'(custom)' if 'destination' in cfg else '(system default)'}")
|
|
185
|
+
print(f" âĒ Remote Repo: {get_repo() or 'None (using bundled offline vault)'}")
|
|
186
|
+
print(f" âĒ Config File: {Path.home() / '.heroalgorithm' / 'config.json'}\n")
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
191
|
+
parser = argparse.ArgumentParser(
|
|
192
|
+
prog="heroalgorithm",
|
|
193
|
+
description="HeroAlgorithm: Instant AI/ML Lab Problem Sheets, Datasets & Code Vault.",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"-v", "--version", action="version", version=f"%(prog)s {__version__}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
200
|
+
|
|
201
|
+
# download subcommand (default action)
|
|
202
|
+
p_dl = subparsers.add_parser("download", help="Download / extract problem sheets")
|
|
203
|
+
p_dl.add_argument(
|
|
204
|
+
"subject",
|
|
205
|
+
nargs="?",
|
|
206
|
+
default=None,
|
|
207
|
+
help="Problem sheet (e.g. ps-1, 1)",
|
|
208
|
+
)
|
|
209
|
+
p_dl.add_argument(
|
|
210
|
+
"-o",
|
|
211
|
+
"--output",
|
|
212
|
+
default=None,
|
|
213
|
+
help="Target folder (default: C:\\AIML_Assignments)",
|
|
214
|
+
)
|
|
215
|
+
p_dl.add_argument(
|
|
216
|
+
"-r",
|
|
217
|
+
"--repo",
|
|
218
|
+
default=None,
|
|
219
|
+
help="Remote GitHub repository ('owner/repo')",
|
|
220
|
+
)
|
|
221
|
+
p_dl.add_argument(
|
|
222
|
+
"-b",
|
|
223
|
+
"--branch",
|
|
224
|
+
default=None,
|
|
225
|
+
help="Branch name (default: main)",
|
|
226
|
+
)
|
|
227
|
+
p_dl.add_argument(
|
|
228
|
+
"-t",
|
|
229
|
+
"--token",
|
|
230
|
+
default=None,
|
|
231
|
+
help="GitHub PAT for private repos",
|
|
232
|
+
)
|
|
233
|
+
p_dl.add_argument(
|
|
234
|
+
"-f",
|
|
235
|
+
"--force",
|
|
236
|
+
action="store_true",
|
|
237
|
+
default=True,
|
|
238
|
+
help="Overwrite existing files",
|
|
239
|
+
)
|
|
240
|
+
p_dl.add_argument(
|
|
241
|
+
"-q",
|
|
242
|
+
"--quiet",
|
|
243
|
+
action="store_true",
|
|
244
|
+
help="Run silently without output",
|
|
245
|
+
)
|
|
246
|
+
p_dl.set_defaults(func=handle_download)
|
|
247
|
+
|
|
248
|
+
# list subcommand
|
|
249
|
+
p_list = subparsers.add_parser("list", help="List available problem sheets and files")
|
|
250
|
+
p_list.add_argument("-r", "--repo", default=None, help="Remote GitHub repository")
|
|
251
|
+
p_list.add_argument("-b", "--branch", default=None, help="Branch name")
|
|
252
|
+
p_list.add_argument("-t", "--token", default=None, help="GitHub PAT for private repos")
|
|
253
|
+
p_list.set_defaults(func=handle_list)
|
|
254
|
+
|
|
255
|
+
# config subcommand
|
|
256
|
+
p_cfg = subparsers.add_parser("config", help="View or modify saved settings")
|
|
257
|
+
p_cfg.add_argument("--set-dest", metavar="PATH", help="Set default destination directory")
|
|
258
|
+
p_cfg.add_argument("--set-repo", metavar="OWNER/REPO", help="Set default remote repository")
|
|
259
|
+
p_cfg.add_argument("--set-branch", metavar="BRANCH", help="Set default branch")
|
|
260
|
+
p_cfg.add_argument("--reset", action="store_true", help="Reset configuration back to defaults")
|
|
261
|
+
p_cfg.set_defaults(func=handle_config)
|
|
262
|
+
|
|
263
|
+
return parser
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
267
|
+
if argv is None:
|
|
268
|
+
argv = sys.argv[1:]
|
|
269
|
+
else:
|
|
270
|
+
argv = list(argv)
|
|
271
|
+
|
|
272
|
+
subcommands = {"download", "list", "config", "-h", "--help", "-v", "--version"}
|
|
273
|
+
|
|
274
|
+
# Seamless shortcut routing:
|
|
275
|
+
# 1. `heroalgorithm` -> `heroalgorithm download`
|
|
276
|
+
# 2. `heroalgorithm 1` -> `heroalgorithm download 1`
|
|
277
|
+
# 3. `heroalgorithm -o <path>` -> `heroalgorithm download -o <path>`
|
|
278
|
+
if not argv:
|
|
279
|
+
argv = ["download"]
|
|
280
|
+
elif argv[0] not in subcommands:
|
|
281
|
+
if not argv[0].startswith("-"):
|
|
282
|
+
argv = ["download"] + argv
|
|
283
|
+
elif argv[0] not in {"-h", "--help", "-v", "--version"}:
|
|
284
|
+
argv = ["download"] + argv
|
|
285
|
+
|
|
286
|
+
parser = build_parser()
|
|
287
|
+
args = parser.parse_args(argv)
|
|
288
|
+
return args.func(args)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
sys.exit(main())
|
heroalgorithm/config.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for HeroAlgorithm.
|
|
3
|
+
Supports user configuration stored in ~/.heroalgorithm/config.json,
|
|
4
|
+
environment variables, and command-line overrides.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict
|
|
14
|
+
|
|
15
|
+
CONFIG_DIR = Path.home() / ".heroalgorithm"
|
|
16
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_system_default_destination() -> Path:
|
|
20
|
+
r"""
|
|
21
|
+
Default target folder for assignments:
|
|
22
|
+
- Windows: C:\AIML_Assignments (falls back to ~/AIML_Assignments if C:\ is not writable)
|
|
23
|
+
- macOS / Linux: ~/AIML_Assignments
|
|
24
|
+
"""
|
|
25
|
+
if sys.platform == "win32":
|
|
26
|
+
candidate = Path("C:/AIML_Assignments")
|
|
27
|
+
try:
|
|
28
|
+
candidate.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
return candidate
|
|
30
|
+
except (PermissionError, OSError):
|
|
31
|
+
return Path.home() / "AIML_Assignments"
|
|
32
|
+
return Path.home() / "AIML_Assignments"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_config() -> Dict[str, Any]:
|
|
36
|
+
"""Load configuration from ~/.heroalgorithm/config.json."""
|
|
37
|
+
if not CONFIG_FILE.exists():
|
|
38
|
+
return {}
|
|
39
|
+
try:
|
|
40
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
41
|
+
data = json.load(f)
|
|
42
|
+
return data if isinstance(data, dict) else {}
|
|
43
|
+
except (json.JSONDecodeError, OSError):
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def save_config(config: Dict[str, Any]) -> None:
|
|
48
|
+
"""Save configuration to ~/.heroalgorithm/config.json."""
|
|
49
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
51
|
+
json.dump(config, f, indent=2)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_destination(cli_dest: str | None = None) -> Path:
|
|
55
|
+
r"""
|
|
56
|
+
Resolve target directory for problem sheets:
|
|
57
|
+
1. CLI argument (--output / -o)
|
|
58
|
+
2. Environment variable HEROALGORITHM_DEST
|
|
59
|
+
3. User saved config
|
|
60
|
+
4. System default (C:\AIML_Assignments on Windows)
|
|
61
|
+
"""
|
|
62
|
+
if cli_dest:
|
|
63
|
+
return Path(cli_dest).expanduser().resolve()
|
|
64
|
+
env_dest = os.getenv("HEROALGORITHM_DEST")
|
|
65
|
+
if env_dest:
|
|
66
|
+
return Path(env_dest).expanduser().resolve()
|
|
67
|
+
saved = load_config().get("destination")
|
|
68
|
+
if saved:
|
|
69
|
+
return Path(saved).expanduser().resolve()
|
|
70
|
+
return get_system_default_destination().resolve()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_repo(cli_repo: str | None = None) -> str | None:
|
|
74
|
+
"""
|
|
75
|
+
Resolve remote GitHub repository if configured.
|
|
76
|
+
"""
|
|
77
|
+
if cli_repo:
|
|
78
|
+
return cli_repo.strip()
|
|
79
|
+
env_repo = os.getenv("HEROALGORITHM_REPO")
|
|
80
|
+
if env_repo:
|
|
81
|
+
return env_repo.strip()
|
|
82
|
+
saved = load_config().get("repo")
|
|
83
|
+
if saved:
|
|
84
|
+
return saved.strip()
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_branch(cli_branch: str | None = None) -> str:
|
|
89
|
+
if cli_branch:
|
|
90
|
+
return cli_branch.strip()
|
|
91
|
+
env_branch = os.getenv("HEROALGORITHM_BRANCH")
|
|
92
|
+
if env_branch:
|
|
93
|
+
return env_branch.strip()
|
|
94
|
+
saved = load_config().get("branch")
|
|
95
|
+
if saved:
|
|
96
|
+
return saved.strip()
|
|
97
|
+
return "main"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_token(cli_token: str | None = None) -> str | None:
|
|
101
|
+
if cli_token:
|
|
102
|
+
return cli_token.strip()
|
|
103
|
+
env_token = os.getenv("HEROALGORITHM_TOKEN") or os.getenv("GITHUB_TOKEN")
|
|
104
|
+
if env_token:
|
|
105
|
+
return env_token.strip()
|
|
106
|
+
saved = load_config().get("token")
|
|
107
|
+
if saved:
|
|
108
|
+
return saved.strip()
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def set_destination(dest: str) -> None:
|
|
113
|
+
cfg = load_config()
|
|
114
|
+
cfg["destination"] = str(Path(dest).expanduser().resolve())
|
|
115
|
+
save_config(cfg)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def set_repo(repo: str) -> None:
|
|
119
|
+
cfg = load_config()
|
|
120
|
+
cfg["repo"] = repo.strip()
|
|
121
|
+
save_config(cfg)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def set_branch(branch: str) -> None:
|
|
125
|
+
cfg = load_config()
|
|
126
|
+
cfg["branch"] = branch.strip()
|
|
127
|
+
save_config(cfg)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def reset_config() -> None:
|
|
131
|
+
if CONFIG_FILE.exists():
|
|
132
|
+
CONFIG_FILE.unlink()
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Downloader and extractor engine for HeroAlgorithm.
|
|
3
|
+
Extracts bundled AIML assignments or synchronizes with remote GitHub repositories safely.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import io
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.request
|
|
13
|
+
import zipfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Dict, List, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
USER_AGENT = "HeroAlgorithm-CLI/0.1.0"
|
|
18
|
+
MAX_ARCHIVE_SIZE_BYTES = 300 * 1024 * 1024 # 300 MB limit
|
|
19
|
+
MAX_FILE_COUNT = 10000 # 10,000 files limit
|
|
20
|
+
|
|
21
|
+
BUNDLED_DIR = Path(__file__).parent / "assignments"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HeroAlgorithmError(Exception):
|
|
25
|
+
"""Base exception for HeroAlgorithm operations."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_repo_string(repo_str: str) -> Tuple[str, str]:
|
|
30
|
+
"""Parse 'owner/repo' or GitHub URL into (owner, repo)."""
|
|
31
|
+
cleaned = repo_str.strip().rstrip("/")
|
|
32
|
+
if cleaned.startswith("https://github.com/"):
|
|
33
|
+
cleaned = cleaned[len("https://github.com/"):].rstrip(".git")
|
|
34
|
+
elif cleaned.startswith("http://github.com/"):
|
|
35
|
+
cleaned = cleaned[len("http://github.com/"):].rstrip(".git")
|
|
36
|
+
|
|
37
|
+
parts = cleaned.split("/")
|
|
38
|
+
if len(parts) != 2 or not parts[0] or not parts[1]:
|
|
39
|
+
raise HeroAlgorithmError(
|
|
40
|
+
f"Invalid repository format '{repo_str}'. Expected 'owner/repo'."
|
|
41
|
+
)
|
|
42
|
+
return parts[0], parts[1]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def matches_filter(first_segment: str, filter_norm: Optional[str]) -> bool:
|
|
46
|
+
"""Normalize and match problem sheet identifiers (e.g. ps-1, 1, ps1)."""
|
|
47
|
+
if not filter_norm:
|
|
48
|
+
return True
|
|
49
|
+
first_segment = first_segment.strip().lower()
|
|
50
|
+
|
|
51
|
+
digits_in_filt = "".join(filter(str.isdigit, filter_norm))
|
|
52
|
+
digits_in_seg = "".join(filter(str.isdigit, first_segment))
|
|
53
|
+
|
|
54
|
+
if digits_in_filt and digits_in_seg:
|
|
55
|
+
if filter_norm.isdigit() or filter_norm.startswith("ps") or "problem" in filter_norm:
|
|
56
|
+
return int(digits_in_seg) == int(digits_in_filt)
|
|
57
|
+
|
|
58
|
+
clean_segment = first_segment.replace("-", "").replace("_", "").replace(" ", "")
|
|
59
|
+
clean_filter = filter_norm.replace("-", "").replace("_", "").replace(" ", "")
|
|
60
|
+
return clean_segment == clean_filter or clean_filter in clean_segment
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def extract_bundled(
|
|
64
|
+
destination: Path,
|
|
65
|
+
subject_filter: Optional[str] = None,
|
|
66
|
+
overwrite: bool = True,
|
|
67
|
+
) -> List[Path]:
|
|
68
|
+
"""
|
|
69
|
+
Extract bundled problem sheets directly from the installed package
|
|
70
|
+
to the target destination folder cleanly and silently.
|
|
71
|
+
"""
|
|
72
|
+
if not BUNDLED_DIR.exists():
|
|
73
|
+
raise HeroAlgorithmError("Bundled assignments directory not found inside package.")
|
|
74
|
+
|
|
75
|
+
destination = destination.expanduser().resolve()
|
|
76
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
filter_norm = subject_filter.strip().lower() if subject_filter else None
|
|
79
|
+
extracted: List[Path] = []
|
|
80
|
+
|
|
81
|
+
for item in BUNDLED_DIR.iterdir():
|
|
82
|
+
rel_name = item.name
|
|
83
|
+
if rel_name.startswith(".") or rel_name == "__pycache__":
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
if filter_norm and item.is_dir() and not matches_filter(rel_name, filter_norm):
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
target = destination / rel_name
|
|
90
|
+
if item.is_dir():
|
|
91
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
for sub_file in item.rglob("*"):
|
|
93
|
+
if sub_file.name.startswith(".") or "__pycache__" in sub_file.parts:
|
|
94
|
+
continue
|
|
95
|
+
sub_rel = sub_file.relative_to(item)
|
|
96
|
+
sub_target = target / sub_rel
|
|
97
|
+
if sub_file.is_dir():
|
|
98
|
+
sub_target.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
else:
|
|
100
|
+
sub_target.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
if sub_target.exists() and not overwrite:
|
|
102
|
+
continue
|
|
103
|
+
shutil.copy2(sub_file, sub_target)
|
|
104
|
+
extracted.append(sub_target)
|
|
105
|
+
else:
|
|
106
|
+
if target.exists() and not overwrite:
|
|
107
|
+
continue
|
|
108
|
+
shutil.copy2(item, target)
|
|
109
|
+
extracted.append(target)
|
|
110
|
+
|
|
111
|
+
return extracted
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def fetch_archive(
|
|
115
|
+
owner: str,
|
|
116
|
+
repo: str,
|
|
117
|
+
branch: str = "main",
|
|
118
|
+
token: Optional[str] = None,
|
|
119
|
+
timeout: int = 60,
|
|
120
|
+
silent: bool = True,
|
|
121
|
+
) -> bytes:
|
|
122
|
+
"""Download zipball from GitHub."""
|
|
123
|
+
if token:
|
|
124
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/zipball/{branch}"
|
|
125
|
+
headers = {
|
|
126
|
+
"User-Agent": USER_AGENT,
|
|
127
|
+
"Authorization": f"Bearer {token}",
|
|
128
|
+
"Accept": "application/vnd.github+json",
|
|
129
|
+
}
|
|
130
|
+
else:
|
|
131
|
+
url = f"https://codeload.github.com/{owner}/{repo}/zip/refs/heads/{branch}"
|
|
132
|
+
headers = {
|
|
133
|
+
"User-Agent": USER_AGENT,
|
|
134
|
+
"Accept": "*/*",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
request = urllib.request.Request(url, headers=headers)
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
with urllib.request.urlopen(request, timeout=timeout) as response:
|
|
141
|
+
chunks: List[bytes] = []
|
|
142
|
+
downloaded = 0
|
|
143
|
+
chunk_size = 64 * 1024
|
|
144
|
+
|
|
145
|
+
while True:
|
|
146
|
+
chunk = response.read(chunk_size)
|
|
147
|
+
if not chunk:
|
|
148
|
+
break
|
|
149
|
+
chunks.append(chunk)
|
|
150
|
+
downloaded += len(chunk)
|
|
151
|
+
if downloaded > MAX_ARCHIVE_SIZE_BYTES:
|
|
152
|
+
raise HeroAlgorithmError("Remote archive exceeds safety limit.")
|
|
153
|
+
|
|
154
|
+
return b"".join(chunks)
|
|
155
|
+
|
|
156
|
+
except urllib.error.HTTPError as e:
|
|
157
|
+
if e.code == 404:
|
|
158
|
+
raise HeroAlgorithmError(f"Repository or branch not found: '{owner}/{repo}'.") from e
|
|
159
|
+
elif e.code in (401, 403):
|
|
160
|
+
raise HeroAlgorithmError(f"Access denied ({e.code}) for '{owner}/{repo}'.") from e
|
|
161
|
+
else:
|
|
162
|
+
raise HeroAlgorithmError(f"HTTP Error {e.code} while connecting to GitHub.") from e
|
|
163
|
+
except urllib.error.URLError as e:
|
|
164
|
+
raise HeroAlgorithmError(f"Network error: {e.reason}") from e
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def is_safe_path(target_path: Path, base_dir: Path) -> bool:
|
|
168
|
+
try:
|
|
169
|
+
target_path.resolve().relative_to(base_dir.resolve())
|
|
170
|
+
return True
|
|
171
|
+
except ValueError:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_archive_root_prefix(archive: zipfile.ZipFile) -> str:
|
|
176
|
+
names = archive.namelist()
|
|
177
|
+
if not names:
|
|
178
|
+
return ""
|
|
179
|
+
first = names[0].split("/")[0]
|
|
180
|
+
prefix = f"{first}/"
|
|
181
|
+
if all(name.startswith(prefix) or name == first for name in names):
|
|
182
|
+
return prefix
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def extract_archive(
|
|
187
|
+
archive_data: bytes,
|
|
188
|
+
destination: Path,
|
|
189
|
+
subject_filter: Optional[str] = None,
|
|
190
|
+
overwrite: bool = True,
|
|
191
|
+
) -> List[Path]:
|
|
192
|
+
"""Extract files safely and silently."""
|
|
193
|
+
try:
|
|
194
|
+
archive = zipfile.ZipFile(io.BytesIO(archive_data))
|
|
195
|
+
except zipfile.BadZipFile as e:
|
|
196
|
+
raise HeroAlgorithmError("Invalid archive payload.") from e
|
|
197
|
+
|
|
198
|
+
members = archive.infolist()
|
|
199
|
+
if len(members) > MAX_FILE_COUNT:
|
|
200
|
+
raise HeroAlgorithmError(f"Archive exceeds {MAX_FILE_COUNT} files limit.")
|
|
201
|
+
|
|
202
|
+
destination = destination.expanduser().resolve()
|
|
203
|
+
destination.mkdir(parents=True, exist_ok=True)
|
|
204
|
+
|
|
205
|
+
root_prefix = get_archive_root_prefix(archive)
|
|
206
|
+
extracted: List[Path] = []
|
|
207
|
+
filter_norm = subject_filter.strip().lower() if subject_filter else None
|
|
208
|
+
|
|
209
|
+
for member in members:
|
|
210
|
+
filename = member.filename
|
|
211
|
+
rel_name = filename[len(root_prefix):] if root_prefix and filename.startswith(root_prefix) else filename
|
|
212
|
+
rel_name = rel_name.lstrip("/\\")
|
|
213
|
+
|
|
214
|
+
if not rel_name or rel_name.startswith(".git") or "__pycache__" in rel_name:
|
|
215
|
+
continue
|
|
216
|
+
|
|
217
|
+
first_seg = rel_name.split("/")[0]
|
|
218
|
+
if filter_norm and not matches_filter(first_seg, filter_norm):
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
target_path = (destination / rel_name).resolve()
|
|
222
|
+
if not is_safe_path(target_path, destination):
|
|
223
|
+
raise HeroAlgorithmError(f"Illegal path traversal detected in '{filename}'.")
|
|
224
|
+
|
|
225
|
+
if member.is_dir():
|
|
226
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
230
|
+
if target_path.exists() and not overwrite:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
with archive.open(member) as src, open(target_path, "wb") as dst:
|
|
234
|
+
dst.write(src.read())
|
|
235
|
+
|
|
236
|
+
extracted.append(target_path)
|
|
237
|
+
|
|
238
|
+
return extracted
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def inspect_archive(archive_data: bytes) -> Dict[str, List[str]]:
|
|
242
|
+
"""Inspect archive without extracting."""
|
|
243
|
+
try:
|
|
244
|
+
archive = zipfile.ZipFile(io.BytesIO(archive_data))
|
|
245
|
+
except zipfile.BadZipFile as e:
|
|
246
|
+
raise HeroAlgorithmError("Invalid archive payload.") from e
|
|
247
|
+
|
|
248
|
+
root_prefix = get_archive_root_prefix(archive)
|
|
249
|
+
structure: Dict[str, List[str]] = {}
|
|
250
|
+
|
|
251
|
+
for member in archive.infolist():
|
|
252
|
+
rel_name = member.filename
|
|
253
|
+
if root_prefix and rel_name.startswith(root_prefix):
|
|
254
|
+
rel_name = rel_name[len(root_prefix):]
|
|
255
|
+
|
|
256
|
+
rel_name = rel_name.strip("/")
|
|
257
|
+
if not rel_name:
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
parts = rel_name.split("/")
|
|
261
|
+
subject = parts[0] if len(parts) > 1 else "(Root)"
|
|
262
|
+
if member.is_dir():
|
|
263
|
+
if subject not in structure:
|
|
264
|
+
structure[subject] = []
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
file_display = "/".join(parts[1:]) if len(parts) > 1 else parts[0]
|
|
268
|
+
structure.setdefault(subject, []).append(file_display)
|
|
269
|
+
|
|
270
|
+
return structure
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: heroalgorithm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Instant AI/ML lab problem sheets, datasets, notebooks, and algorithms vault.
|
|
5
|
+
Author: HeroAlgorithm Maintainer
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/AdityaxDeore/heroalgorithm
|
|
8
|
+
Project-URL: Repository, https://github.com/AdityaxDeore/heroalgorithm.git
|
|
9
|
+
Keywords: heroalgorithm,aiml,machine-learning,problem-sheets,datasets,jupyter-notebook,algorithms,cli
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Education
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Education
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# ⥠HeroAlgorithm
|
|
29
|
+
|
|
30
|
+
> **Instant AI/ML Problem Sheets, Datasets & Code Vault**
|
|
31
|
+
> Download, extract, and organize all your AI & Machine Learning lab assignments, problem sheets, datasets, Jupyter Notebooks (`.ipynb`), and Python scripts cleanly onto your PC with a single command.
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/heroalgorithm/)
|
|
34
|
+
[](https://www.python.org/)
|
|
35
|
+
[](https://opensource.org/licenses/MIT)
|
|
36
|
+
[]()
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## ð The Magic Experience
|
|
41
|
+
|
|
42
|
+
Install and organize all your AI/ML lab problem sheets into `C:\AIML_Assignments` in one step:
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
pip install heroalgorithm; heroalgorithm
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or run `heroalgorithm` anytime in your terminal:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
heroalgorithm
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
And your C: drive folder is cleanly populated:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
C:\AIML_Assignments\
|
|
58
|
+
âââ PS-1/
|
|
59
|
+
â âââ dataset/
|
|
60
|
+
â â âââ data.csv
|
|
61
|
+
â âââ notebook.ipynb
|
|
62
|
+
â âââ solution.py
|
|
63
|
+
â âââ README.md
|
|
64
|
+
âââ PS-2/
|
|
65
|
+
â âââ dataset/
|
|
66
|
+
â â âââ housing.csv
|
|
67
|
+
â âââ notebook.ipynb
|
|
68
|
+
â âââ solution.py
|
|
69
|
+
â âââ README.md
|
|
70
|
+
âââ PS-3/
|
|
71
|
+
âââ dataset/
|
|
72
|
+
â âââ iris.csv
|
|
73
|
+
âââ notebook.ipynb
|
|
74
|
+
âââ solution.py
|
|
75
|
+
âââ README.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> **Clean & Quiet by Design:** No noisy spam or "installing file x" lines. Everything stays hidden and extracts quietly into your folder!
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## ð Key Features
|
|
83
|
+
|
|
84
|
+
- ⥠**Bundled Offline Vault**: Problem sheets, datasets, notebooks, and solutions are packaged directly inside the package. Works 100% offline without needing GitHub.
|
|
85
|
+
- ð **Automatic C: Drive Placement**: Automatically organizes files into `C:\AIML_Assignments` on Windows (or `~/AIML_Assignments`).
|
|
86
|
+
- ðŊ **Selective Extraction**: Run `heroalgorithm 1` or `heroalgorithm ps-1` to unpack just Problem Sheet 1.
|
|
87
|
+
- ðĪŦ **Silent Mode**: Zero annoying logs. Use `-q` or `--quiet` for 100% silent execution.
|
|
88
|
+
- ð **GitHub Sync (Optional)**: Connect to a remote GitHub repository anytime with `heroalgorithm config --set-repo <user>/<repo>` to sync the latest assignments.
|
|
89
|
+
- ðŠķ **Zero Dependencies**: 100% built on Python's standard library. Instant installation.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ð ïļ Usage Examples
|
|
94
|
+
|
|
95
|
+
### 1. Extract All Problem Sheets to C:\AIML_Assignments
|
|
96
|
+
```powershell
|
|
97
|
+
heroalgorithm
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2. Extract Only Problem Sheet 1
|
|
101
|
+
```powershell
|
|
102
|
+
# Shorthand:
|
|
103
|
+
heroalgorithm 1
|
|
104
|
+
|
|
105
|
+
# Or:
|
|
106
|
+
heroalgorithm ps-1
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 3. Choose a Custom Output Folder
|
|
110
|
+
```powershell
|
|
111
|
+
heroalgorithm -o "D:\MyCollege\AI_Lab"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Run Completely Silently
|
|
115
|
+
```powershell
|
|
116
|
+
heroalgorithm -q
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 5. Preview Available Problem Sheets
|
|
120
|
+
```powershell
|
|
121
|
+
heroalgorithm list
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 6. Configure Default Destination or Remote Repo
|
|
125
|
+
```powershell
|
|
126
|
+
# Change default destination:
|
|
127
|
+
heroalgorithm config --set-dest "D:\AIML_Lab"
|
|
128
|
+
|
|
129
|
+
# Connect to a GitHub repository:
|
|
130
|
+
heroalgorithm config --set-repo your-username/aiml-assignments
|
|
131
|
+
|
|
132
|
+
# Check active configuration:
|
|
133
|
+
heroalgorithm config
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## ðĒ Publishing to PyPI (Step-by-Step)
|
|
139
|
+
|
|
140
|
+
The package name **`heroalgorithm`** is verified available on PyPI!
|
|
141
|
+
|
|
142
|
+
### Step 1: Build the Package
|
|
143
|
+
```powershell
|
|
144
|
+
python -m build
|
|
145
|
+
```
|
|
146
|
+
This creates the distribution files in `dist/`:
|
|
147
|
+
- `heroalgorithm-0.1.0-py3-none-any.whl`
|
|
148
|
+
- `heroalgorithm-0.1.0.tar.gz`
|
|
149
|
+
|
|
150
|
+
### Step 2: Upload to PyPI
|
|
151
|
+
```powershell
|
|
152
|
+
python -m twine upload dist/*
|
|
153
|
+
```
|
|
154
|
+
*(Enter `__token__` as the username, and paste your PyPI API Token as the password).*
|
|
155
|
+
|
|
156
|
+
Once published, anyone in the world can run:
|
|
157
|
+
```powershell
|
|
158
|
+
pip install heroalgorithm; heroalgorithm
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## ð License
|
|
164
|
+
|
|
165
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
heroalgorithm/__init__.py,sha256=--Ia2TfkFLGnLww0Kz8jCPLB9Z-w-y5l5xQ-c7oNctg,132
|
|
2
|
+
heroalgorithm/cli.py,sha256=aGqie9mHpEIe4I8nbLMcxZIkBdCLJous6-Jjm8ihvn4,9359
|
|
3
|
+
heroalgorithm/config.py,sha256=PZhzuQUE3y-E88QcPzI9PlHjXbJUk8XIL_IT2o9iw2M,3703
|
|
4
|
+
heroalgorithm/downloader.py,sha256=DMv00LuD_EXURMpL2O7ZTomsYdfIctnOl-nPR6DNQeo,9197
|
|
5
|
+
heroalgorithm/assignments/README.md,sha256=xxe71YI_Ohnp1CjrdLmPfQLDPEJasdy4HyU5-_2sfqo,668
|
|
6
|
+
heroalgorithm/assignments/PS-1/README.md,sha256=pQYdMwXqpzBc_G2-VrQcqwuXPXkbdOIer0gq2F2gQYc,365
|
|
7
|
+
heroalgorithm/assignments/PS-1/notebook.ipynb,sha256=U1_UtlY88eRYjsMQaSxKYWpICX364ZT4Lx2oUhYVBmE,634
|
|
8
|
+
heroalgorithm/assignments/PS-1/solution.py,sha256=A9Vibm9diUCrG7fGPzBaAFBgR2kJRGuYzYEagRCnpIg,691
|
|
9
|
+
heroalgorithm/assignments/PS-1/dataset/data.csv,sha256=tAb2VmXVN0QD3KuphgL3rhbJKeQr7b28XKs9DMb4KUQ,85
|
|
10
|
+
heroalgorithm/assignments/PS-2/README.md,sha256=I6qu7UfC1Yk6g8-sMhB9TCRHe2jSDiXvdWdyYkuAt1c,134
|
|
11
|
+
heroalgorithm/assignments/PS-2/notebook.ipynb,sha256=XNzUC1Hp5AFFqHKG5EEiKiS2IXIs3N-BN1k1_ovtsbA,643
|
|
12
|
+
heroalgorithm/assignments/PS-2/solution.py,sha256=ChhaOS9q8B0Baj8sTJbmbram9LhoHYZFpxtOkDw03qM,294
|
|
13
|
+
heroalgorithm/assignments/PS-2/dataset/housing.csv,sha256=ZC-rDpAbVtyk2jnFweeLECkRhTuHGP6HuIufroDJ3XQ,80
|
|
14
|
+
heroalgorithm/assignments/PS-3/README.md,sha256=ypthFXdc5S_kiUzetXYwcFAaJDXMBjYlDN2We0Z-qdw,141
|
|
15
|
+
heroalgorithm/assignments/PS-3/notebook.ipynb,sha256=EFFvCQyv0im3eiXgcNlNb-gJ1ZXIDjrUQwDn-fpgLUk,629
|
|
16
|
+
heroalgorithm/assignments/PS-3/solution.py,sha256=kWg-e78iBmRxFRm2dqtxpziDAERHxyHtyoHplV3auZE,289
|
|
17
|
+
heroalgorithm/assignments/PS-3/dataset/iris.csv,sha256=4yQWcLCQdulLfqX8RbVvD2LF0gsqsWf8kagMgurhjTc,138
|
|
18
|
+
heroalgorithm-0.1.0.dist-info/licenses/LICENSE,sha256=kV-nU8WE--XMKqR83AfoIBw9xwjGDLVwaXotCTrIaRM,1081
|
|
19
|
+
heroalgorithm-0.1.0.dist-info/METADATA,sha256=WQHq5z-wCiOy2NSO50dLAMNYCivthw8V8M3NnCXPBio,5203
|
|
20
|
+
heroalgorithm-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
21
|
+
heroalgorithm-0.1.0.dist-info/entry_points.txt,sha256=rO5Idb7pe9dHY7pf1aubIdFWYdwT-12XVH36Owv4HSo,87
|
|
22
|
+
heroalgorithm-0.1.0.dist-info/top_level.txt,sha256=VzSRNFjblVRX-YX3fYqMmNGx3a84whGUIHpvmw3mEDE,14
|
|
23
|
+
heroalgorithm-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AssignVault Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
heroalgorithm
|