projectdevsetup 1.0.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,11 @@
1
+ """
2
+ @project projectdevsetup
3
+ @org Zenith Open Source Projects
4
+ @license MIT License
5
+ @author roshhellwett
6
+ """
7
+
8
+ __version__ = "1.0.0"
9
+ __author__ = "roshhellwett"
10
+ __org__ = "Zenith Open Source Projects"
11
+ __email__ = "roshhellwett@icloud.com"
@@ -0,0 +1,31 @@
1
+ """
2
+ @project projectdevsetup
3
+ @org Zenith Open Source Projects
4
+ @license MIT License
5
+ """
6
+
7
+ import sys
8
+ from projectdevsetup.wizard import run
9
+ from projectdevsetup.utils.logger import error
10
+
11
+
12
+ def main():
13
+ try:
14
+ run()
15
+ except KeyboardInterrupt:
16
+ print("\n")
17
+ error(
18
+ "Setup cancelled by user. Run 'python -m projectdevsetup' to start again."
19
+ )
20
+ sys.exit(0)
21
+ except Exception as e:
22
+ error(
23
+ "Something unexpected went wrong. "
24
+ "Please report this issue at:\n"
25
+ " https://github.com/zenith-open-source/projectdevsetup/issues"
26
+ )
27
+ sys.exit(1)
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,102 @@
1
+ """
2
+ @project projectdevsetup
3
+ @org Zenith Open Source Projects
4
+ @license MIT License
5
+ """
6
+
7
+ import subprocess
8
+ import platform
9
+ import shutil
10
+ from projectdevsetup.utils.logger import already_installed, info
11
+
12
+
13
+ def is_installed(command: str, version_flag: str = "--version") -> bool:
14
+ """
15
+ Check if a command-line tool is installed and working.
16
+ Returns True if tool exists and responds to version flag.
17
+ """
18
+ try:
19
+ if not shutil.which(command):
20
+ return False
21
+ result = subprocess.run(
22
+ [command, version_flag], capture_output=True, text=True, timeout=10
23
+ )
24
+ return result.returncode == 0
25
+ except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
26
+ return False
27
+
28
+
29
+ def check_python() -> bool:
30
+ cmds = ["python3", "python"]
31
+ for cmd in cmds:
32
+ if is_installed(cmd):
33
+ return True
34
+ return False
35
+
36
+
37
+ def check_gcc() -> bool:
38
+ return is_installed("gcc")
39
+
40
+
41
+ def check_gpp() -> bool:
42
+ return is_installed("g++")
43
+
44
+
45
+ def check_java() -> bool:
46
+ return is_installed("java", "-version")
47
+
48
+
49
+ def check_node() -> bool:
50
+ return is_installed("node")
51
+
52
+
53
+ def check_npm() -> bool:
54
+ return is_installed("npm")
55
+
56
+
57
+ def check_rust() -> bool:
58
+ return is_installed("rustc")
59
+
60
+
61
+ def check_cargo() -> bool:
62
+ return is_installed("cargo")
63
+
64
+
65
+ def check_go() -> bool:
66
+ return is_installed("go", "version")
67
+
68
+
69
+ def check_vscode() -> bool:
70
+ """Check if VSCode (code command) is available."""
71
+ return bool(shutil.which("code"))
72
+
73
+
74
+ def check_winget() -> bool:
75
+ return is_installed("winget", "--version")
76
+
77
+
78
+ def check_brew() -> bool:
79
+ return is_installed("brew", "--version")
80
+
81
+
82
+ def check_snap() -> bool:
83
+ return is_installed("snap", "--version")
84
+
85
+
86
+ def get_installed_summary() -> dict:
87
+ """Return a full summary of what is installed."""
88
+ return {
89
+ "python": check_python(),
90
+ "gcc": check_gcc(),
91
+ "gpp": check_gpp(),
92
+ "java": check_java(),
93
+ "node": check_node(),
94
+ "npm": check_npm(),
95
+ "rust": check_rust(),
96
+ "cargo": check_cargo(),
97
+ "go": check_go(),
98
+ "vscode": check_vscode(),
99
+ "winget": check_winget(),
100
+ "brew": check_brew(),
101
+ "snap": check_snap(),
102
+ }
@@ -0,0 +1,447 @@
1
+ """
2
+ @project projectdevsetup
3
+ @org Zenith Open Source Projects
4
+ @license MIT License
5
+ """
6
+
7
+ import subprocess
8
+ import platform
9
+ import os
10
+ from pathlib import Path
11
+ from projectdevsetup.detector import (
12
+ check_gcc,
13
+ check_gpp,
14
+ check_java,
15
+ check_node,
16
+ check_npm,
17
+ check_rust,
18
+ check_cargo,
19
+ check_go,
20
+ check_python,
21
+ )
22
+ from projectdevsetup.network import download_file, get_temp_dir
23
+ from projectdevsetup.path_manager import add_to_path, verify_in_path
24
+ from projectdevsetup.utils.logger import (
25
+ info,
26
+ success,
27
+ error,
28
+ warning,
29
+ already_installed,
30
+ step,
31
+ )
32
+ from projectdevsetup.utils.os_detect import detect_system, SystemInfo
33
+
34
+ URLS = {
35
+ "jdk_windows": "https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.exe",
36
+ "jdk_mac_arm": "https://download.oracle.com/java/21/latest/jdk-21_macos-aarch64_bin.dmg",
37
+ "jdk_mac_x64": "https://download.oracle.com/java/21/latest/jdk-21_macos-x64_bin.dmg",
38
+ "node_windows": "https://nodejs.org/dist/latest/node-latest-x86.msi",
39
+ "go_windows": "https://go.dev/dl/go1.22.0.windows-amd64.msi",
40
+ "go_mac": "https://go.dev/dl/go1.22.0.darwin-amd64.pkg",
41
+ }
42
+
43
+
44
+ class Installer:
45
+ def __init__(self):
46
+ self.sys_info: SystemInfo = detect_system()
47
+
48
+ def install_for_language(self, language: str) -> bool:
49
+ """
50
+ Install all required tools for a given language.
51
+ Returns True if installation succeeded.
52
+ """
53
+ lang = language.lower()
54
+ handlers = {
55
+ "python": self._install_python,
56
+ "c": self._install_gcc,
57
+ "cpp": self._install_gcc,
58
+ "java": self._install_java,
59
+ "html": self._install_html,
60
+ "javascript": self._install_node,
61
+ "rust": self._install_rust,
62
+ "go": self._install_go,
63
+ }
64
+ handler = handlers.get(lang)
65
+ if handler:
66
+ return handler()
67
+ error(f"Unknown language: {language}")
68
+ return False
69
+
70
+ def _install_python(self) -> bool:
71
+ if check_python():
72
+ already_installed("Python")
73
+ return True
74
+ info("Installing Python...")
75
+ os_name = self.sys_info.os_name
76
+ if os_name == "windows":
77
+ return self._install_python_windows()
78
+ elif os_name == "linux":
79
+ return self._install_via_package_manager(
80
+ ["python3", "python3-pip", "python3-venv"], "Python"
81
+ )
82
+ elif os_name == "mac":
83
+ return self._install_via_brew("python3", "Python")
84
+ return False
85
+
86
+ def _install_python_windows(self) -> bool:
87
+ tmp = get_temp_dir()
88
+ arch = "amd64" if self.sys_info.is_64bit else "win32"
89
+ url = f"https://www.python.org/ftp/python/3.12.0/python-3.12.0-{arch}.exe"
90
+ installer = tmp / "python_installer.exe"
91
+ if not download_file(url, installer, "Python"):
92
+ return False
93
+ try:
94
+ subprocess.run(
95
+ [
96
+ str(installer),
97
+ "/quiet",
98
+ "InstallAllUsers=1",
99
+ "PrependPath=1",
100
+ "Include_test=0",
101
+ ],
102
+ timeout=300,
103
+ )
104
+ if check_python():
105
+ success("Python installed successfully!")
106
+ return True
107
+ except Exception:
108
+ pass
109
+ self._show_manual_install("Python", "https://www.python.org/downloads/")
110
+ return False
111
+
112
+ def _install_gcc(self) -> bool:
113
+ if check_gcc() and check_gpp():
114
+ already_installed("GCC / G++")
115
+ return True
116
+ info("Installing GCC and G++...")
117
+ os_name = self.sys_info.os_name
118
+ if os_name == "windows":
119
+ return self._install_gcc_windows()
120
+ elif os_name == "linux":
121
+ return self._install_via_package_manager(
122
+ ["gcc", "g++", "build-essential"]
123
+ if self.sys_info.package_manager == "apt"
124
+ else ["gcc", "g++"],
125
+ "GCC",
126
+ )
127
+ elif os_name == "mac":
128
+ return self._install_xcode_cli_tools()
129
+ return False
130
+
131
+ def _install_gcc_windows(self) -> bool:
132
+ """Install MSYS2 + GCC on Windows."""
133
+ info("Installing MSYS2 (includes GCC for Windows)...")
134
+ tmp = get_temp_dir()
135
+ arch = "x86_64" if self.sys_info.is_64bit else "i686"
136
+ url = (
137
+ f"https://github.com/msys2/msys2-installer/releases/download/"
138
+ f"2024-01-13/msys2-{arch}-20240113.exe"
139
+ )
140
+ installer = tmp / "msys2_installer.exe"
141
+ if not download_file(url, installer, "MSYS2 (GCC)"):
142
+ self._show_manual_install(
143
+ "GCC for Windows (MSYS2)", "https://www.msys2.org/"
144
+ )
145
+ return False
146
+ try:
147
+ subprocess.run(
148
+ [
149
+ str(installer),
150
+ "install",
151
+ "--root",
152
+ "C:\\msys64",
153
+ "--confirm-command",
154
+ ],
155
+ timeout=600,
156
+ )
157
+ subprocess.run(
158
+ [
159
+ "C:\\msys64\\usr\\bin\\pacman.exe",
160
+ "-S",
161
+ "--noconfirm",
162
+ "mingw-w64-x86_64-gcc",
163
+ ],
164
+ timeout=300,
165
+ )
166
+ add_to_path("C:\\msys64\\mingw64\\bin", "GCC")
167
+ if verify_in_path("gcc"):
168
+ success("GCC installed successfully!")
169
+ return True
170
+ except Exception:
171
+ pass
172
+ self._show_manual_install("GCC (MSYS2)", "https://www.msys2.org/")
173
+ return False
174
+
175
+ def _install_xcode_cli_tools(self) -> bool:
176
+ """Install Xcode Command Line Tools on Mac (includes GCC)."""
177
+ info("Installing Xcode Command Line Tools (includes GCC)...")
178
+ try:
179
+ subprocess.run(["xcode-select", "--install"], timeout=30)
180
+ warning(
181
+ "A popup appeared asking to install developer tools. "
182
+ "Please click 'Install' and wait for it to finish. "
183
+ "Then run this tool again."
184
+ )
185
+ return False
186
+ except Exception:
187
+ self._show_manual_install(
188
+ "Xcode Command Line Tools", "https://developer.apple.com/xcode/"
189
+ )
190
+ return False
191
+
192
+ def _install_java(self) -> bool:
193
+ if check_java():
194
+ already_installed("Java JDK")
195
+ return True
196
+ info("Installing Java JDK 21 LTS...")
197
+ os_name = self.sys_info.os_name
198
+ if os_name == "windows":
199
+ return self._install_java_windows()
200
+ elif os_name == "linux":
201
+ return self._install_via_package_manager(
202
+ ["default-jdk"]
203
+ if self.sys_info.package_manager == "apt"
204
+ else ["java-21-openjdk"],
205
+ "Java JDK",
206
+ )
207
+ elif os_name == "mac":
208
+ return self._install_via_brew("openjdk@21", "Java JDK 21")
209
+ return False
210
+
211
+ def _install_java_windows(self) -> bool:
212
+ tmp = get_temp_dir()
213
+ url = URLS["jdk_windows"]
214
+ installer = tmp / "jdk_installer.exe"
215
+ if not download_file(url, installer, "Java JDK 21"):
216
+ self._show_manual_install("Java JDK", "https://adoptium.net/")
217
+ return False
218
+ try:
219
+ subprocess.run([str(installer), "/s"], timeout=300)
220
+ warning(
221
+ "Java JDK installed. "
222
+ "Please restart your terminal for it to be available."
223
+ )
224
+ return True
225
+ except Exception:
226
+ self._show_manual_install("Java JDK", "https://adoptium.net/")
227
+ return False
228
+
229
+ def _install_html(self) -> bool:
230
+ success(
231
+ "HTML and CSS do not need any installation! "
232
+ "Just VSCode with Live Server extension is enough."
233
+ )
234
+ return True
235
+
236
+ def _install_node(self) -> bool:
237
+ if check_node() and check_npm():
238
+ already_installed("Node.js and npm")
239
+ return True
240
+ info("Installing Node.js (includes npm)...")
241
+ os_name = self.sys_info.os_name
242
+ if os_name == "windows":
243
+ return self._install_node_windows()
244
+ elif os_name == "linux":
245
+ return self._install_node_linux()
246
+ elif os_name == "mac":
247
+ return self._install_via_brew("node", "Node.js")
248
+ return False
249
+
250
+ def _install_node_windows(self) -> bool:
251
+ tmp = get_temp_dir()
252
+ arch = "x64" if self.sys_info.is_64bit else "x86"
253
+ url = f"https://nodejs.org/dist/latest/node-latest-{arch}.msi"
254
+ installer = tmp / "node_installer.msi"
255
+ if not download_file(url, installer, "Node.js"):
256
+ self._show_manual_install("Node.js", "https://nodejs.org/")
257
+ return False
258
+ try:
259
+ subprocess.run(["msiexec", "/i", str(installer), "/quiet"], timeout=300)
260
+ if check_node():
261
+ success("Node.js installed successfully!")
262
+ return True
263
+ except Exception:
264
+ pass
265
+ self._show_manual_install("Node.js", "https://nodejs.org/")
266
+ return False
267
+
268
+ def _install_node_linux(self) -> bool:
269
+ try:
270
+ setup = subprocess.run(
271
+ [
272
+ "bash",
273
+ "-c",
274
+ "curl -fsSL https://deb.nodesource.com/setup_lts.x"
275
+ " | sudo -E bash -",
276
+ ],
277
+ timeout=120,
278
+ )
279
+ return self._install_via_package_manager(["nodejs"], "Node.js")
280
+ except Exception:
281
+ return self._install_via_package_manager(["nodejs", "npm"], "Node.js")
282
+
283
+ def _install_rust(self) -> bool:
284
+ if check_rust() and check_cargo():
285
+ already_installed("Rust and Cargo")
286
+ return True
287
+ info("Installing Rust via rustup...")
288
+ os_name = self.sys_info.os_name
289
+ if os_name == "windows":
290
+ return self._install_rust_windows()
291
+ else:
292
+ return self._install_rust_unix()
293
+
294
+ def _install_rust_windows(self) -> bool:
295
+ tmp = get_temp_dir()
296
+ url = "https://win.rustup.rs/x86_64"
297
+ installer = tmp / "rustup-init.exe"
298
+ if not download_file(url, installer, "Rust (rustup)"):
299
+ self._show_manual_install("Rust", "https://rustup.rs/")
300
+ return False
301
+ try:
302
+ subprocess.run([str(installer), "-y"], timeout=600)
303
+ cargo_bin = Path.home() / ".cargo" / "bin"
304
+ add_to_path(str(cargo_bin), "Rust/Cargo")
305
+ if check_rust():
306
+ success("Rust installed successfully!")
307
+ return True
308
+ except Exception:
309
+ pass
310
+ self._show_manual_install("Rust", "https://rustup.rs/")
311
+ return False
312
+
313
+ def _install_rust_unix(self) -> bool:
314
+ try:
315
+ subprocess.run(
316
+ [
317
+ "bash",
318
+ "-c",
319
+ "curl --proto '=https' --tlsv1.2 "
320
+ "-sSf https://sh.rustup.rs | sh -s -- -y",
321
+ ],
322
+ timeout=600,
323
+ )
324
+ cargo_bin = Path.home() / ".cargo" / "bin"
325
+ add_to_path(str(cargo_bin), "Rust/Cargo")
326
+ if check_rust():
327
+ success("Rust installed successfully!")
328
+ return True
329
+ except Exception:
330
+ pass
331
+ self._show_manual_install("Rust", "https://rustup.rs/")
332
+ return False
333
+
334
+ def _install_go(self) -> bool:
335
+ if check_go():
336
+ already_installed("Go")
337
+ return True
338
+ info("Installing Go...")
339
+ os_name = self.sys_info.os_name
340
+ if os_name == "windows":
341
+ return self._install_go_windows()
342
+ elif os_name == "linux":
343
+ return self._install_via_package_manager(["golang-go"], "Go")
344
+ elif os_name == "mac":
345
+ return self._install_via_brew("go", "Go")
346
+ return False
347
+
348
+ def _install_go_windows(self) -> bool:
349
+ tmp = get_temp_dir()
350
+ arch = "amd64" if self.sys_info.is_64bit else "386"
351
+ url = f"https://go.dev/dl/go1.22.0.windows-{arch}.msi"
352
+ installer = tmp / "go_installer.msi"
353
+ if not download_file(url, installer, "Go"):
354
+ self._show_manual_install("Go", "https://go.dev/dl/")
355
+ return False
356
+ try:
357
+ subprocess.run(["msiexec", "/i", str(installer), "/quiet"], timeout=300)
358
+ add_to_path("C:\\Go\\bin", "Go")
359
+ if check_go():
360
+ success("Go installed successfully!")
361
+ return True
362
+ except Exception:
363
+ pass
364
+ self._show_manual_install("Go", "https://go.dev/dl/")
365
+ return False
366
+
367
+ def _install_via_package_manager(self, packages: list, tool_name: str) -> bool:
368
+ """Install packages using the system package manager."""
369
+ pm = self.sys_info.package_manager
370
+ pkg_commands = {
371
+ "apt": ["sudo", "apt-get", "install", "-y"],
372
+ "pacman": ["sudo", "pacman", "-S", "--noconfirm"],
373
+ "dnf": ["sudo", "dnf", "install", "-y"],
374
+ "yum": ["sudo", "yum", "install", "-y"],
375
+ "zypper": ["sudo", "zypper", "install", "-y"],
376
+ "apk": ["sudo", "apk", "add"],
377
+ }
378
+ cmd_prefix = pkg_commands.get(pm)
379
+ if not cmd_prefix:
380
+ warning(
381
+ f"No supported package manager found to install {tool_name}. "
382
+ "Please install it manually."
383
+ )
384
+ return False
385
+ try:
386
+ if pm == "apt":
387
+ subprocess.run(
388
+ ["sudo", "apt-get", "update"], capture_output=True, timeout=60
389
+ )
390
+ result = subprocess.run(cmd_prefix + packages, timeout=300)
391
+ if result.returncode == 0:
392
+ success(f"{tool_name} installed successfully!")
393
+ return True
394
+ except subprocess.TimeoutExpired:
395
+ error(
396
+ f"Installation of {tool_name} took too long. "
397
+ "Please try again or install manually."
398
+ )
399
+ except Exception:
400
+ pass
401
+ self._show_manual_install(tool_name, "")
402
+ return False
403
+
404
+ def _install_via_brew(self, formula: str, tool_name: str) -> bool:
405
+ """Install via Homebrew on Mac."""
406
+ from projectdevsetup.detector import check_brew
407
+
408
+ if not check_brew():
409
+ warning("Homebrew is not installed. Installing Homebrew first...")
410
+ try:
411
+ subprocess.run(
412
+ [
413
+ "/bin/bash",
414
+ "-c",
415
+ "$(curl -fsSL https://raw.githubusercontent.com/"
416
+ "Homebrew/install/HEAD/install.sh)",
417
+ ],
418
+ timeout=600,
419
+ )
420
+ except Exception:
421
+ self._show_manual_install("Homebrew", "https://brew.sh")
422
+ return False
423
+ try:
424
+ result = subprocess.run(["brew", "install", formula], timeout=300)
425
+ if result.returncode == 0:
426
+ success(f"{tool_name} installed successfully!")
427
+ return True
428
+ except Exception:
429
+ pass
430
+ self._show_manual_install(tool_name, "")
431
+ return False
432
+
433
+ def _show_manual_install(self, tool_name: str, url: str):
434
+ """Show a beginner-friendly message for manual install."""
435
+ warning(f"Could not install {tool_name} automatically.")
436
+ if url:
437
+ print(
438
+ f"\n Please download and install {tool_name} manually:\n"
439
+ f" {url}\n"
440
+ f"\n After installing, run this tool again.\n"
441
+ )
442
+ try:
443
+ import webbrowser
444
+
445
+ webbrowser.open(url)
446
+ except Exception:
447
+ pass
@@ -0,0 +1,80 @@
1
+ """
2
+ @project projectdevsetup
3
+ @org Zenith Open Source Projects
4
+ @license MIT License
5
+ """
6
+
7
+ import urllib.request
8
+ import urllib.error
9
+ import os
10
+ import tempfile
11
+ from pathlib import Path
12
+ from projectdevsetup.utils.logger import info, error, warning
13
+
14
+
15
+ def check_internet() -> bool:
16
+ """
17
+ Check if internet is available.
18
+ Tries to reach multiple hosts in case one is down.
19
+ """
20
+ hosts = [
21
+ "https://www.google.com",
22
+ "https://www.cloudflare.com",
23
+ "https://www.microsoft.com",
24
+ ]
25
+ for host in hosts:
26
+ try:
27
+ urllib.request.urlopen(host, timeout=5)
28
+ return True
29
+ except Exception:
30
+ continue
31
+ return False
32
+
33
+
34
+ def download_file(
35
+ url: str, destination: Path, description: str, retries: int = 3
36
+ ) -> bool:
37
+ """
38
+ Download a file with retry logic and progress indication.
39
+ Returns True on success, False on failure.
40
+ Shows beginner-friendly messages throughout.
41
+ """
42
+ for attempt in range(1, retries + 1):
43
+ try:
44
+ info(f"Downloading {description}... (attempt {attempt}/{retries})")
45
+
46
+ def progress_hook(count, block_size, total_size):
47
+ if total_size > 0:
48
+ percent = min(int(count * block_size * 100 / total_size), 100)
49
+ print(f"\r Progress: {percent}% ", end="", flush=True)
50
+
51
+ urllib.request.urlretrieve(url, str(destination), reporthook=progress_hook)
52
+ print()
53
+ return True
54
+
55
+ except urllib.error.URLError:
56
+ warning(
57
+ f"Download attempt {attempt} failed. "
58
+ f"Checking your internet connection..."
59
+ )
60
+ if not check_internet():
61
+ error(
62
+ "No internet connection detected. "
63
+ "Please connect to the internet and try again."
64
+ )
65
+ return False
66
+ except Exception as e:
67
+ warning(f"Download attempt {attempt} failed. Retrying...")
68
+
69
+ error(
70
+ f"Could not download {description} after {retries} attempts. "
71
+ "Please check your internet connection and try again."
72
+ )
73
+ return False
74
+
75
+
76
+ def get_temp_dir() -> Path:
77
+ """Return a safe temp directory for downloads."""
78
+ tmp = Path(tempfile.gettempdir()) / "projectdevsetup"
79
+ tmp.mkdir(parents=True, exist_ok=True)
80
+ return tmp