dancealgorithm 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.
Files changed (74) hide show
  1. dancealgorithm/__init__.py +128 -0
  2. dancealgorithm/__main__.py +8 -0
  3. dancealgorithm/assignments/DA - Campus/Solution.java +198 -0
  4. dancealgorithm/assignments/DA - Campus/__init__.py +34 -0
  5. dancealgorithm/assignments/DA - Flight/Solution.java +204 -0
  6. dancealgorithm/assignments/DA - Flight/__init__.py +34 -0
  7. dancealgorithm/assignments/DA - Smart/Solution.java +220 -0
  8. dancealgorithm/assignments/DA - Smart/__init__.py +34 -0
  9. dancealgorithm/assignments/DA.txt +29 -0
  10. dancealgorithm/assignments/FK - Advertisement/Solution.java +141 -0
  11. dancealgorithm/assignments/FK - Advertisement/__init__.py +34 -0
  12. dancealgorithm/assignments/FK - Electric/Solution.java +168 -0
  13. dancealgorithm/assignments/FK - Electric/__init__.py +34 -0
  14. dancealgorithm/assignments/FK - Emergency/Solution.java +159 -0
  15. dancealgorithm/assignments/FK - Emergency/__init__.py +34 -0
  16. dancealgorithm/assignments/FK.txt +26 -0
  17. dancealgorithm/assignments/GC - Classroom/Solution.java +87 -0
  18. dancealgorithm/assignments/GC - Classroom/__init__.py +34 -0
  19. dancealgorithm/assignments/GC - Interference/Solution.java +88 -0
  20. dancealgorithm/assignments/GC - Interference/__init__.py +34 -0
  21. dancealgorithm/assignments/GC - University/Solution.java +88 -0
  22. dancealgorithm/assignments/GC - University/__init__.py +34 -0
  23. dancealgorithm/assignments/GC.txt +25 -0
  24. dancealgorithm/assignments/K - Cloud/Solution.java +115 -0
  25. dancealgorithm/assignments/K - Cloud/__init__.py +34 -0
  26. dancealgorithm/assignments/K - Disaster/Solution.java +116 -0
  27. dancealgorithm/assignments/K - Disaster/__init__.py +34 -0
  28. dancealgorithm/assignments/K - Hospital/Solution.java +115 -0
  29. dancealgorithm/assignments/K - Hospital/__init__.py +34 -0
  30. dancealgorithm/assignments/K.txt +38 -0
  31. dancealgorithm/assignments/LC - Delivery/Solution.java +180 -0
  32. dancealgorithm/assignments/LC - Delivery/__init__.py +34 -0
  33. dancealgorithm/assignments/LC - Drilling/Solution.java +197 -0
  34. dancealgorithm/assignments/LC - Drilling/__init__.py +34 -0
  35. dancealgorithm/assignments/LC - Flight/Solution.java +180 -0
  36. dancealgorithm/assignments/LC - Flight/__init__.py +34 -0
  37. dancealgorithm/assignments/LC.txt +31 -0
  38. dancealgorithm/assignments/MS - Commerce/Solution.java +162 -0
  39. dancealgorithm/assignments/MS - Commerce/__init__.py +34 -0
  40. dancealgorithm/assignments/MS - Commerce/dataset.csv +16 -0
  41. dancealgorithm/assignments/MS - Customer/Solution.java +144 -0
  42. dancealgorithm/assignments/MS - Customer/__init__.py +34 -0
  43. dancealgorithm/assignments/MS - Customer/dataset.csv +16 -0
  44. dancealgorithm/assignments/MS - Library/Solution.java +162 -0
  45. dancealgorithm/assignments/MS - Library/__init__.py +34 -0
  46. dancealgorithm/assignments/MS - Library/dataset.csv +16 -0
  47. dancealgorithm/assignments/MS.txt +32 -0
  48. dancealgorithm/assignments/MSG - Emergency/Solution.java +211 -0
  49. dancealgorithm/assignments/MSG - Emergency/__init__.py +34 -0
  50. dancealgorithm/assignments/MSG - Logistics/Solution.java +189 -0
  51. dancealgorithm/assignments/MSG - Logistics/__init__.py +34 -0
  52. dancealgorithm/assignments/MSG - Skyconnect/Solution.java +204 -0
  53. dancealgorithm/assignments/MSG - Skyconnect/__init__.py +34 -0
  54. dancealgorithm/assignments/MSG.txt +28 -0
  55. dancealgorithm/assignments/QS - Commerce/Solution.java +147 -0
  56. dancealgorithm/assignments/QS - Commerce/__init__.py +34 -0
  57. dancealgorithm/assignments/QS - Commerce/dataset.csv +16 -0
  58. dancealgorithm/assignments/QS - Movie/Solution.java +148 -0
  59. dancealgorithm/assignments/QS - Movie/__init__.py +34 -0
  60. dancealgorithm/assignments/QS - Movie/dataset.csv +16 -0
  61. dancealgorithm/assignments/QS - Online/Solution.java +147 -0
  62. dancealgorithm/assignments/QS - Online/__init__.py +34 -0
  63. dancealgorithm/assignments/QS - Online/dataset.csv +16 -0
  64. dancealgorithm/assignments/QS.txt +33 -0
  65. dancealgorithm/assignments/__init__.py +65 -0
  66. dancealgorithm/cli.py +349 -0
  67. dancealgorithm/config.py +125 -0
  68. dancealgorithm/downloader.py +330 -0
  69. dancealgorithm-0.1.0.dist-info/METADATA +199 -0
  70. dancealgorithm-0.1.0.dist-info/RECORD +74 -0
  71. dancealgorithm-0.1.0.dist-info/WHEEL +5 -0
  72. dancealgorithm-0.1.0.dist-info/entry_points.txt +3 -0
  73. dancealgorithm-0.1.0.dist-info/licenses/LICENSE +22 -0
  74. dancealgorithm-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,65 @@
1
+ """
2
+ DanceAlgorithm Bundled Problem Sheets & Assignments Vault.
3
+ Contains all 24 DAA algorithms and problem sheets.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import importlib
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List
10
+
11
+ ASSIGNMENTS_DIR = Path(__file__).resolve().parent
12
+
13
+ REGISTRY: Dict[Any, Any] = {}
14
+ SHEETS: List[Any] = []
15
+ DAA_ASSIGNMENTS: Dict[str, Any] = {}
16
+
17
+ ORDERED_NAMES = [
18
+ "MS - Customer",
19
+ "MS - Commerce",
20
+ "MS - Library",
21
+ "QS - Movie",
22
+ "QS - Commerce",
23
+ "QS - Online",
24
+ "FK - Emergency",
25
+ "FK - Electric",
26
+ "FK - Advertisement",
27
+ "DA - Smart",
28
+ "DA - Campus",
29
+ "DA - Flight",
30
+ "MSG - Logistics",
31
+ "MSG - Skyconnect",
32
+ "MSG - Emergency",
33
+ "K - Disaster",
34
+ "K - Hospital",
35
+ "K - Cloud",
36
+ "GC - Classroom",
37
+ "GC - Interference",
38
+ "GC - University",
39
+ "LC - Delivery",
40
+ "LC - Drilling",
41
+ "LC - Flight",
42
+ ]
43
+
44
+ for idx, name in enumerate(ORDERED_NAMES, 1):
45
+ folder = ASSIGNMENTS_DIR / name
46
+ if folder.is_dir():
47
+ try:
48
+ mod = importlib.import_module(f".{name}", package=__name__)
49
+ globals()[name] = mod
50
+ SHEETS.append(mod)
51
+
52
+ REGISTRY[idx] = mod
53
+ REGISTRY[f"ps{idx}"] = mod
54
+ REGISTRY[f"PS{idx}"] = mod
55
+ REGISTRY[f"ps{idx:02d}"] = mod
56
+ REGISTRY[f"PS{idx:02d}"] = mod
57
+
58
+ norm = name.lower().replace(" ", "").replace("-", "").replace("_", "")
59
+ DAA_ASSIGNMENTS[norm] = mod
60
+ REGISTRY[name] = mod
61
+ REGISTRY[norm] = mod
62
+ except Exception:
63
+ pass
64
+
65
+ __all__ = ["REGISTRY", "SHEETS", "DAA_ASSIGNMENTS", "ASSIGNMENTS_DIR"]
dancealgorithm/cli.py ADDED
@@ -0,0 +1,349 @@
1
+ """
2
+ CLI interface for DanceAlgorithm.
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 dancealgorithm import __version__
13
+ from dancealgorithm.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 dancealgorithm.downloader import (
25
+ BUNDLED_DIR,
26
+ DanceAlgorithmError,
27
+ extract_archive,
28
+ extract_bundled,
29
+ fetch_archive,
30
+ inspect_archive,
31
+ parse_repo_string,
32
+ )
33
+
34
+ # Reconfigure console streams on Windows to prevent cp1252 charmap errors
35
+ if sys.platform == "win32":
36
+ try:
37
+ if hasattr(sys.stdout, "reconfigure"):
38
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
39
+ if hasattr(sys.stderr, "reconfigure"):
40
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace")
41
+ except Exception:
42
+ pass
43
+
44
+
45
+ def run_install(
46
+ destination: Path,
47
+ subject: Optional[str] = None,
48
+ repo_str: Optional[str] = None,
49
+ branch: Optional[str] = None,
50
+ token: Optional[str] = None,
51
+ force: bool = True,
52
+ quiet: bool = False,
53
+ ) -> int:
54
+ """
55
+ Executes assignment extraction and outputs the destination path.
56
+ """
57
+ target_path = destination.resolve()
58
+
59
+ try:
60
+ if repo_str:
61
+ owner, repo = parse_repo_string(repo_str)
62
+ branch = branch or get_branch()
63
+ archive_data = fetch_archive(
64
+ owner=owner,
65
+ repo=repo,
66
+ branch=branch,
67
+ token=token,
68
+ silent=True,
69
+ )
70
+ extracted = extract_archive(
71
+ archive_data=archive_data,
72
+ destination=destination,
73
+ subject_filter=subject,
74
+ overwrite=force,
75
+ )
76
+ else:
77
+ extracted = extract_bundled(
78
+ destination=destination,
79
+ subject_filter=subject,
80
+ overwrite=force,
81
+ )
82
+
83
+ except DanceAlgorithmError as e:
84
+ print(f"Error: {e}", file=sys.stderr)
85
+ return 1
86
+ except Exception as e:
87
+ print(f"Unexpected error: {e}", file=sys.stderr)
88
+ return 1
89
+
90
+ if not extracted:
91
+ if not quiet:
92
+ if subject:
93
+ print(f"Error: No problem sheets found matching '{subject}'.", file=sys.stderr)
94
+ else:
95
+ print("Error: No files were extracted.", file=sys.stderr)
96
+ return 1
97
+
98
+ if not quiet:
99
+ out_path = target_path
100
+ if subject and extracted:
101
+ try:
102
+ sub_parts = [
103
+ p.relative_to(target_path).parts
104
+ for p in extracted
105
+ if len(p.relative_to(target_path).parts) > 1
106
+ ]
107
+ if sub_parts:
108
+ out_path = target_path / sub_parts[0][0]
109
+ except Exception:
110
+ out_path = target_path
111
+ print(out_path)
112
+
113
+ return 0
114
+
115
+
116
+ def check_and_display_text(target: str) -> bool:
117
+ """
118
+ If target matches an algorithm notes .txt file (e.g. DA.txt, da, ms.txt, etc.),
119
+ print its contents directly to the terminal screen without extracting any files.
120
+ """
121
+ if not BUNDLED_DIR.exists():
122
+ return False
123
+
124
+ clean_target = target.strip().replace("/", "\\").split("\\")[-1]
125
+ norm = clean_target.lower().replace("-", "").replace("_", "").replace(" ", "")
126
+ norm_base = norm.removesuffix(".txt")
127
+
128
+ for f in BUNDLED_DIR.glob("*.txt"):
129
+ f_norm = f.name.lower().replace("-", "").replace("_", "").replace(" ", "")
130
+ f_base = f_norm.removesuffix(".txt")
131
+ if norm == f_norm or norm == f_base or norm_base == f_base:
132
+ try:
133
+ print(f.read_text(encoding="utf-8"))
134
+ return True
135
+ except Exception:
136
+ pass
137
+ return False
138
+
139
+
140
+ def handle_download(args: argparse.Namespace) -> int:
141
+ subject = " ".join(args.subject).strip() if isinstance(args.subject, list) else (args.subject or "").strip()
142
+ if not subject:
143
+ subject = None
144
+
145
+ if subject:
146
+ if check_and_display_text(subject):
147
+ return 0
148
+
149
+ destination = get_destination(args.output)
150
+ repo = get_repo(args.repo)
151
+ branch = get_branch(args.branch)
152
+ token = get_token(args.token)
153
+ return run_install(
154
+ destination=destination,
155
+ subject=subject,
156
+ repo_str=repo,
157
+ branch=branch,
158
+ token=token,
159
+ force=args.force,
160
+ quiet=args.quiet,
161
+ )
162
+
163
+
164
+ def handle_list(args: argparse.Namespace) -> int:
165
+ repo_str = get_repo(args.repo)
166
+ if not repo_str:
167
+ print("📚 Available Bundled DAA Problem Sheets:\n")
168
+ if BUNDLED_DIR.exists():
169
+ for folder in sorted(
170
+ [f for f in BUNDLED_DIR.iterdir() if f.is_dir() and not f.name.startswith((".", "__")) and f.name != "__pycache__"],
171
+ key=lambda p: p.name,
172
+ ):
173
+ files = [
174
+ f.relative_to(folder).as_posix()
175
+ for f in folder.rglob("*")
176
+ if f.is_file()
177
+ and "__pycache__" not in f.parts
178
+ and not f.name.endswith((".pyc", ".class"))
179
+ and f.name != "__init__.py"
180
+ ]
181
+ print(f"📁 {folder.name} ({len(files)} files)")
182
+ for f in files:
183
+ print(f" ├── {f}")
184
+
185
+ txt_files = sorted(list(BUNDLED_DIR.glob("*.txt")))
186
+ if txt_files:
187
+ print("\n📝 Available Algorithm Notes & Complexities:")
188
+ for tf in txt_files:
189
+ print(f" • {tf.name} (run: dance {tf.name})")
190
+
191
+ print("\n💡 Tip: Run 'dance <folder-name>' (e.g. 'dance MSG - Logistics') to extract only that folder.")
192
+ print("💡 Tip: Run 'dance <ALGO.txt>' (e.g. 'dance DA.txt') to display algorithm notes directly on screen.")
193
+ return 0
194
+
195
+ try:
196
+ owner, repo = parse_repo_string(repo_str)
197
+ branch = get_branch(args.branch)
198
+ token = get_token(args.token)
199
+ print(f"🔍 Inspecting remote catalogue in {owner}/{repo} (branch: {branch})...\n")
200
+ archive_data = fetch_archive(owner=owner, repo=repo, branch=branch, token=token, silent=True)
201
+ structure = inspect_archive(archive_data)
202
+ print("📚 Available Problem Sheets & Lab Files:")
203
+ for subject, files in sorted(structure.items()):
204
+ print(f"\n📁 {subject} ({len(files)} files)")
205
+ for f in files[:8]:
206
+ print(f" ├── {f}")
207
+ if len(files) > 8:
208
+ print(f" └── ... and {len(files) - 8} more files")
209
+ return 0
210
+ except DanceAlgorithmError as e:
211
+ print(f"❌ Error: {e}")
212
+ return 1
213
+
214
+
215
+ def handle_config(args: argparse.Namespace) -> int:
216
+ if args.reset:
217
+ reset_config()
218
+ print("✅ Configuration reset to defaults.")
219
+ return 0
220
+
221
+ if args.set_dest:
222
+ set_destination(args.set_dest)
223
+ print(f"✅ Default destination set to: '{get_destination()}'")
224
+
225
+ if args.set_repo:
226
+ try:
227
+ parse_repo_string(args.set_repo)
228
+ set_repo(args.set_repo)
229
+ print(f"✅ Default GitHub repo set to: '{args.set_repo}'")
230
+ except DanceAlgorithmError as e:
231
+ print(f"❌ {e}")
232
+ return 1
233
+
234
+ if args.set_branch:
235
+ set_branch(args.set_branch)
236
+ print(f"✅ Default branch set to: '{args.set_branch}'")
237
+
238
+ cfg = load_config()
239
+ print("\n⚙️ DanceAlgorithm Configuration:")
240
+ print(f" • Destination: {get_destination()} {'(custom)' if 'destination' in cfg else '(system default)'}")
241
+ print(f" • Remote Repo: {get_repo() or 'None (using bundled offline vault)'}")
242
+ print(f" • Config File: {Path.home() / '.dancealgorithm' / 'config.json'}\n")
243
+ return 0
244
+
245
+
246
+ def build_parser() -> argparse.ArgumentParser:
247
+ parser = argparse.ArgumentParser(
248
+ prog="dancealgorithm",
249
+ description="DanceAlgorithm: Instant DAA Algorithms, Lab Problem Sheets, Datasets & Code Vault.",
250
+ )
251
+ parser.add_argument(
252
+ "-v", "--version", action="version", version=f"%(prog)s {__version__}"
253
+ )
254
+
255
+ subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
256
+
257
+ # download subcommand (default action)
258
+ p_dl = subparsers.add_parser("download", help="Download / extract problem sheets or view notes")
259
+ p_dl.add_argument(
260
+ "subject",
261
+ nargs="*",
262
+ default=[],
263
+ help="Problem sheet or notes (e.g. 'MSG - Logistics', 'DA - Smart', 'DA.txt')",
264
+ )
265
+ p_dl.add_argument(
266
+ "-o",
267
+ "--output",
268
+ default=None,
269
+ help="Target folder (default: C:\\DAA_Assignments)",
270
+ )
271
+ p_dl.add_argument(
272
+ "-r",
273
+ "--repo",
274
+ default=None,
275
+ help="Remote GitHub repository ('owner/repo')",
276
+ )
277
+ p_dl.add_argument(
278
+ "-b",
279
+ "--branch",
280
+ default=None,
281
+ help="Branch name (default: main)",
282
+ )
283
+ p_dl.add_argument(
284
+ "-t",
285
+ "--token",
286
+ default=None,
287
+ help="GitHub PAT for private repos",
288
+ )
289
+ p_dl.add_argument(
290
+ "-f",
291
+ "--force",
292
+ action="store_true",
293
+ default=True,
294
+ help="Overwrite existing files",
295
+ )
296
+ p_dl.add_argument(
297
+ "-q",
298
+ "--quiet",
299
+ action="store_true",
300
+ help="Run silently without output",
301
+ )
302
+ p_dl.set_defaults(func=handle_download)
303
+
304
+ # list subcommand
305
+ p_list = subparsers.add_parser("list", help="List available problem sheets and notes")
306
+ p_list.add_argument("-r", "--repo", default=None, help="Remote GitHub repository")
307
+ p_list.add_argument("-b", "--branch", default=None, help="Branch name")
308
+ p_list.add_argument("-t", "--token", default=None, help="GitHub PAT for private repos")
309
+ p_list.set_defaults(func=handle_list)
310
+
311
+ # config subcommand
312
+ p_cfg = subparsers.add_parser("config", help="View or modify saved settings")
313
+ p_cfg.add_argument("--set-dest", metavar="PATH", help="Set default destination directory")
314
+ p_cfg.add_argument("--set-repo", metavar="OWNER/REPO", help="Set default remote repository")
315
+ p_cfg.add_argument("--set-branch", metavar="BRANCH", help="Set default branch")
316
+ p_cfg.add_argument("--reset", action="store_true", help="Reset configuration back to defaults")
317
+ p_cfg.set_defaults(func=handle_config)
318
+
319
+ return parser
320
+
321
+
322
+ def main(argv: Optional[List[str]] = None) -> int:
323
+ if argv is None:
324
+ argv = sys.argv[1:]
325
+ else:
326
+ argv = list(argv)
327
+
328
+ subcommands = {"download", "list", "config", "-h", "--help", "-v", "--version"}
329
+
330
+ # Seamless shortcut routing:
331
+ # 1. `dance` -> `dance download`
332
+ # 2. `dance MSG - Logistics` -> `dance download MSG - Logistics`
333
+ # 3. `dance DA.txt` -> `dance download DA.txt`
334
+ # 4. `dance -o <path>` -> `dance download -o <path>`
335
+ if not argv:
336
+ argv = ["download"]
337
+ elif argv[0] not in subcommands:
338
+ if not argv[0].startswith("-"):
339
+ argv = ["download"] + argv
340
+ elif argv[0] not in {"-h", "--help", "-v", "--version"}:
341
+ argv = ["download"] + argv
342
+
343
+ parser = build_parser()
344
+ args = parser.parse_args(argv)
345
+ return args.func(args)
346
+
347
+
348
+ if __name__ == "__main__":
349
+ sys.exit(main())
@@ -0,0 +1,125 @@
1
+ """
2
+ Configuration management for DanceAlgorithm.
3
+ Supports user configuration stored in ~/.dancealgorithm/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() / ".dancealgorithm"
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:\DAA_Assignments (falls back to ~/DAA_Assignments if C:\ is not writable)
23
+ - macOS / Linux: ~/DAA_Assignments
24
+ """
25
+ if sys.platform == "win32":
26
+ candidate = Path("C:/DAA_Assignments")
27
+ try:
28
+ candidate.mkdir(parents=True, exist_ok=True)
29
+ return candidate
30
+ except (PermissionError, OSError):
31
+ return Path.home() / "DAA_Assignments"
32
+ return Path.home() / "DAA_Assignments"
33
+
34
+
35
+ def load_config() -> Dict[str, Any]:
36
+ """Load configuration from ~/.dancealgorithm/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 ~/.dancealgorithm/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 DANCEALGORITHM_DEST
59
+ 3. User saved config
60
+ 4. System default (C:\DAA_Assignments on Windows)
61
+ """
62
+ if cli_dest:
63
+ return Path(cli_dest)
64
+
65
+ env_dest = os.environ.get("DANCEALGORITHM_DEST")
66
+ if env_dest:
67
+ return Path(env_dest)
68
+
69
+ cfg = load_config()
70
+ if "destination" in cfg and cfg["destination"]:
71
+ return Path(cfg["destination"])
72
+
73
+ return get_system_default_destination()
74
+
75
+
76
+ def set_destination(dest_path: str) -> Path:
77
+ """Persist a new default target folder in ~/.dancealgorithm/config.json."""
78
+ target = Path(dest_path).expanduser().resolve()
79
+ cfg = load_config()
80
+ cfg["destination"] = str(target)
81
+ save_config(cfg)
82
+ return target
83
+
84
+
85
+ def get_repo(cli_repo: str | None = None) -> str | None:
86
+ if cli_repo:
87
+ return cli_repo
88
+ env_repo = os.environ.get("DANCEALGORITHM_REPO")
89
+ if env_repo:
90
+ return env_repo
91
+ cfg = load_config()
92
+ return cfg.get("repo")
93
+
94
+
95
+ def set_repo(repo_str: str) -> None:
96
+ cfg = load_config()
97
+ cfg["repo"] = repo_str.strip()
98
+ save_config(cfg)
99
+
100
+
101
+ def get_branch(cli_branch: str | None = None) -> str:
102
+ if cli_branch:
103
+ return cli_branch
104
+ env_branch = os.environ.get("DANCEALGORITHM_BRANCH")
105
+ if env_branch:
106
+ return env_branch
107
+ cfg = load_config()
108
+ return cfg.get("branch", "main")
109
+
110
+
111
+ def set_branch(branch_str: str) -> None:
112
+ cfg = load_config()
113
+ cfg["branch"] = branch_str.strip()
114
+ save_config(cfg)
115
+
116
+
117
+ def get_token(cli_token: str | None = None) -> str | None:
118
+ if cli_token:
119
+ return cli_token
120
+ return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
121
+
122
+
123
+ def reset_config() -> None:
124
+ if CONFIG_FILE.exists():
125
+ CONFIG_FILE.unlink()