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.
@@ -0,0 +1,6 @@
1
+ """
2
+ HeroAlgorithm - Instant AI/ML Lab Problem Sheets, Datasets & Code Manager.
3
+ """
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["__version__"]
@@ -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,7 @@
1
+ feature1,feature2,target
2
+ 1.2,3.4,0
3
+ 2.5,1.1,0
4
+ 5.8,7.9,1
5
+ 6.1,8.3,1
6
+ 1.9,2.4,0
7
+ 7.3,9.1,1
@@ -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,5 @@
1
+ # Problem Sheet 2 (PS-2)
2
+
3
+ ## Objectives
4
+ - Decision Trees and Random Forests on housing data.
5
+ - Evaluate MAE, RMSE, and R2 score.
@@ -0,0 +1,5 @@
1
+ area,bedrooms,price
2
+ 1200,2,250000
3
+ 1800,3,380000
4
+ 2400,4,520000
5
+ 850,1,160000
@@ -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,5 @@
1
+ # Problem Sheet 3 (PS-3)
2
+
3
+ ## Objectives
4
+ - Clustering (K-Means) and Classification on Iris dataset.
5
+ - Dimensionality Reduction with PCA.
@@ -0,0 +1,4 @@
1
+ sepal_length,sepal_width,petal_length,petal_width,species
2
+ 5.1,3.5,1.4,0.2,setosa
3
+ 7.0,3.2,4.7,1.4,versicolor
4
+ 6.3,3.3,6.0,2.5,virginica
@@ -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())
@@ -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
+ [![PyPI Version](https://img.shields.io/pypi/v/heroalgorithm.svg)](https://pypi.org/project/heroalgorithm/)
34
+ [![Python Version](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
36
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0%20runtime-brightgreen.svg)]()
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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ hero = heroalgorithm.cli:main
3
+ heroalgorithm = heroalgorithm.cli:main
@@ -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