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.
- projectdevsetup/__init__.py +11 -0
- projectdevsetup/__main__.py +31 -0
- projectdevsetup/detector.py +102 -0
- projectdevsetup/installer.py +447 -0
- projectdevsetup/network.py +80 -0
- projectdevsetup/path_manager.py +149 -0
- projectdevsetup/permissions.py +95 -0
- projectdevsetup/templates/Hello.java +8 -0
- projectdevsetup/templates/__init__.py +5 -0
- projectdevsetup/templates/hello.c +9 -0
- projectdevsetup/templates/hello.cpp +9 -0
- projectdevsetup/templates/hello.go +10 -0
- projectdevsetup/templates/hello.html +25 -0
- projectdevsetup/templates/hello.js +4 -0
- projectdevsetup/templates/hello.py +4 -0
- projectdevsetup/templates/hello.rs +6 -0
- projectdevsetup/utils/__init__.py +5 -0
- projectdevsetup/utils/logger.py +69 -0
- projectdevsetup/utils/os_detect.py +142 -0
- projectdevsetup/utils/venv_helper.py +95 -0
- projectdevsetup/vscode.py +386 -0
- projectdevsetup/wizard.py +262 -0
- projectdevsetup-1.0.0.dist-info/METADATA +83 -0
- projectdevsetup-1.0.0.dist-info/RECORD +27 -0
- projectdevsetup-1.0.0.dist-info/WHEEL +5 -0
- projectdevsetup-1.0.0.dist-info/licenses/LICENSE +22 -0
- projectdevsetup-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from projectdevsetup.utils.logger import success, warning, info, error
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add_to_path(new_path: str, tool_name: str):
|
|
15
|
+
"""
|
|
16
|
+
Add a directory to PATH permanently on all 3 OS.
|
|
17
|
+
Falls back to showing manual instructions if automatic fails.
|
|
18
|
+
"""
|
|
19
|
+
os_name = platform.system().lower()
|
|
20
|
+
|
|
21
|
+
if os_name == "windows":
|
|
22
|
+
_add_to_path_windows(new_path, tool_name)
|
|
23
|
+
elif os_name in ("linux", "darwin"):
|
|
24
|
+
_add_to_path_unix(new_path, tool_name)
|
|
25
|
+
else:
|
|
26
|
+
_show_manual_path_instructions(new_path, tool_name)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _add_to_path_windows(new_path: str, tool_name: str):
|
|
30
|
+
"""Add to PATH permanently on Windows via registry."""
|
|
31
|
+
try:
|
|
32
|
+
import winreg
|
|
33
|
+
|
|
34
|
+
key = winreg.OpenKey(
|
|
35
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
36
|
+
r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment",
|
|
37
|
+
0,
|
|
38
|
+
winreg.KEY_ALL_ACCESS,
|
|
39
|
+
)
|
|
40
|
+
current_path, _ = winreg.QueryValueEx(key, "Path")
|
|
41
|
+
if new_path.lower() not in current_path.lower():
|
|
42
|
+
new_value = current_path + ";" + new_path
|
|
43
|
+
winreg.SetValueEx(key, "Path", 0, winreg.REG_EXPAND_SZ, new_value)
|
|
44
|
+
winreg.CloseKey(key)
|
|
45
|
+
success(
|
|
46
|
+
f"{tool_name} added to PATH. "
|
|
47
|
+
"Please restart your terminal for this to take effect."
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
success(f"{tool_name} is already in PATH.")
|
|
51
|
+
except PermissionError:
|
|
52
|
+
_show_manual_path_instructions_windows(new_path, tool_name)
|
|
53
|
+
except ImportError:
|
|
54
|
+
_show_manual_path_instructions_windows(new_path, tool_name)
|
|
55
|
+
except Exception:
|
|
56
|
+
_show_manual_path_instructions_windows(new_path, tool_name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _add_to_path_unix(new_path: str, tool_name: str):
|
|
60
|
+
"""Add to PATH permanently on Linux/Mac via shell profile."""
|
|
61
|
+
home = Path.home()
|
|
62
|
+
shell = os.environ.get("SHELL", "")
|
|
63
|
+
|
|
64
|
+
if "zsh" in shell:
|
|
65
|
+
profiles = [home / ".zshrc", home / ".zprofile"]
|
|
66
|
+
elif "fish" in shell:
|
|
67
|
+
profiles = [home / ".config" / "fish" / "config.fish"]
|
|
68
|
+
else:
|
|
69
|
+
profiles = [home / ".bashrc", home / ".bash_profile", home / ".profile"]
|
|
70
|
+
|
|
71
|
+
export_line = f'\nexport PATH="{new_path}:$PATH"\n'
|
|
72
|
+
fish_line = f'\nset -gx PATH "{new_path}" $PATH\n'
|
|
73
|
+
|
|
74
|
+
written = False
|
|
75
|
+
for profile in profiles:
|
|
76
|
+
try:
|
|
77
|
+
if profile.exists():
|
|
78
|
+
content = profile.read_text()
|
|
79
|
+
if new_path not in content:
|
|
80
|
+
with open(profile, "a") as f:
|
|
81
|
+
if "fish" in str(profile):
|
|
82
|
+
f.write(fish_line)
|
|
83
|
+
else:
|
|
84
|
+
f.write(export_line)
|
|
85
|
+
written = True
|
|
86
|
+
break
|
|
87
|
+
except Exception:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
if not written:
|
|
91
|
+
try:
|
|
92
|
+
profile = home / ".profile"
|
|
93
|
+
with open(profile, "a") as f:
|
|
94
|
+
f.write(export_line)
|
|
95
|
+
written = True
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
if written:
|
|
100
|
+
success(
|
|
101
|
+
f"{tool_name} added to PATH. "
|
|
102
|
+
"Please restart your terminal for changes to take effect."
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
_show_manual_path_instructions(new_path, tool_name)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _show_manual_path_instructions(new_path: str, tool_name: str):
|
|
109
|
+
"""Show plain English instructions for adding to PATH manually."""
|
|
110
|
+
warning(
|
|
111
|
+
f"Could not automatically add {tool_name} to PATH. Please follow these steps:"
|
|
112
|
+
)
|
|
113
|
+
print(
|
|
114
|
+
f"\n Add this line to your terminal config file "
|
|
115
|
+
f"(~/.bashrc or ~/.zshrc):\n"
|
|
116
|
+
f'\n export PATH="{new_path}:$PATH"\n'
|
|
117
|
+
f"\n Then close and reopen your terminal.\n"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _show_manual_path_instructions_windows(new_path: str, tool_name: str):
|
|
122
|
+
"""Show plain English instructions for Windows PATH."""
|
|
123
|
+
warning(f"Could not automatically add {tool_name} to PATH.")
|
|
124
|
+
print(
|
|
125
|
+
f"\n How to add it manually on Windows:\n"
|
|
126
|
+
f" 1. Press Windows key + S, search 'Environment Variables'\n"
|
|
127
|
+
f" 2. Click 'Edit the system environment variables'\n"
|
|
128
|
+
f" 3. Click 'Environment Variables' button\n"
|
|
129
|
+
f" 4. Under 'System variables', find 'Path' and click 'Edit'\n"
|
|
130
|
+
f" 5. Click 'New' and paste this:\n"
|
|
131
|
+
f" {new_path}\n"
|
|
132
|
+
f" 6. Click OK on all windows\n"
|
|
133
|
+
f" 7. Restart your terminal\n"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def verify_in_path(command: str) -> bool:
|
|
138
|
+
"""Verify a command is accessible in PATH after installation."""
|
|
139
|
+
try:
|
|
140
|
+
result = subprocess.run(
|
|
141
|
+
["where", command]
|
|
142
|
+
if platform.system() == "Windows"
|
|
143
|
+
else ["which", command],
|
|
144
|
+
capture_output=True,
|
|
145
|
+
text=True,
|
|
146
|
+
)
|
|
147
|
+
return result.returncode == 0
|
|
148
|
+
except Exception:
|
|
149
|
+
return False
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import subprocess
|
|
10
|
+
from projectdevsetup.utils.logger import error, warning
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def check_admin_windows() -> bool:
|
|
14
|
+
"""Check if running as administrator on Windows."""
|
|
15
|
+
try:
|
|
16
|
+
import ctypes
|
|
17
|
+
|
|
18
|
+
return bool(ctypes.windll.shell32.IsUserAnAdmin())
|
|
19
|
+
except Exception:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_sudo_linux() -> bool:
|
|
24
|
+
"""Check if sudo is available without password on Linux/Mac."""
|
|
25
|
+
try:
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
["sudo", "-n", "true"], capture_output=True, text=True, timeout=5
|
|
28
|
+
)
|
|
29
|
+
return result.returncode == 0
|
|
30
|
+
except Exception:
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_disk_space(required_gb: float = 2.0) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if there is enough disk space.
|
|
37
|
+
Default: require at least 2GB free.
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
import shutil
|
|
41
|
+
|
|
42
|
+
total, used, free = shutil.disk_usage("/")
|
|
43
|
+
free_gb = free / (1024**3)
|
|
44
|
+
if free_gb < required_gb:
|
|
45
|
+
error(
|
|
46
|
+
f"Not enough disk space. "
|
|
47
|
+
f"You have {free_gb:.1f}GB free but we need "
|
|
48
|
+
f"at least {required_gb}GB. "
|
|
49
|
+
f"Please free up some space and try again."
|
|
50
|
+
)
|
|
51
|
+
return False
|
|
52
|
+
return True
|
|
53
|
+
except Exception:
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def assert_not_root() -> bool:
|
|
58
|
+
"""
|
|
59
|
+
Warn if running as root on Linux.
|
|
60
|
+
Running pip packages as root is not recommended.
|
|
61
|
+
"""
|
|
62
|
+
if platform.system() == "Linux":
|
|
63
|
+
try:
|
|
64
|
+
if os.geteuid() == 0:
|
|
65
|
+
warning(
|
|
66
|
+
"You are running as root. This is not recommended. "
|
|
67
|
+
"Please run as a normal user. "
|
|
68
|
+
"Use sudo only when prompted."
|
|
69
|
+
)
|
|
70
|
+
return False
|
|
71
|
+
except AttributeError:
|
|
72
|
+
pass
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def handle_no_admin_windows():
|
|
77
|
+
"""Tell Windows user exactly how to get admin rights."""
|
|
78
|
+
error("Administrator rights are needed to install developer tools on Windows.")
|
|
79
|
+
print(
|
|
80
|
+
"\n How to fix this:\n"
|
|
81
|
+
" 1. Close this window\n"
|
|
82
|
+
" 2. Find your terminal (Command Prompt or PowerShell)\n"
|
|
83
|
+
" 3. Right-click on it\n"
|
|
84
|
+
" 4. Click 'Run as administrator'\n"
|
|
85
|
+
" 5. Run the command again\n"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def handle_no_sudo_linux():
|
|
90
|
+
"""Tell Linux user exactly how to handle sudo."""
|
|
91
|
+
warning(
|
|
92
|
+
"Some installations need sudo (admin) access. "
|
|
93
|
+
"You may be asked for your password during setup. "
|
|
94
|
+
"This is normal and safe."
|
|
95
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<!-- Hello, World! — HTML/CSS -->
|
|
3
|
+
<!-- projectdevsetup by Zenith Open Source Projects -->
|
|
4
|
+
<html lang="en">
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="UTF-8">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<title>My First Web Page</title>
|
|
9
|
+
<style>
|
|
10
|
+
body {
|
|
11
|
+
font-family: Arial, sans-serif;
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
height: 100vh;
|
|
16
|
+
background: #f0f0f0;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
h1 { color: #333; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<h1>Hello, World!</h1>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys
|
|
8
|
+
from colorama import Fore, Style, init
|
|
9
|
+
|
|
10
|
+
init(autoreset=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _safe_print(message: str):
|
|
14
|
+
"""Print with UTF-8 encoding fix for Windows."""
|
|
15
|
+
try:
|
|
16
|
+
print(message)
|
|
17
|
+
except UnicodeEncodeError:
|
|
18
|
+
print(message.encode("ascii", "replace").decode("ascii"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def info(message: str):
|
|
22
|
+
"""Blue info message."""
|
|
23
|
+
_safe_print(f"{Fore.CYAN}[projectdevsetup]{Style.RESET_ALL} {message}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def success(message: str):
|
|
27
|
+
"""Green success message."""
|
|
28
|
+
_safe_print(f"{Fore.GREEN}[OK] {message}{Style.RESET_ALL}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def warning(message: str):
|
|
32
|
+
"""Yellow warning message."""
|
|
33
|
+
_safe_print(f"{Fore.YELLOW}[!] {message}{Style.RESET_ALL}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def error(message: str):
|
|
37
|
+
"""Red error message in plain English. Never show raw exceptions."""
|
|
38
|
+
_safe_print(f"{Fore.RED}[X] {message}{Style.RESET_ALL}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def step(number: int, total: int, message: str):
|
|
42
|
+
"""Shows progress like: [2/5] Installing GCC..."""
|
|
43
|
+
_safe_print(f"{Fore.BLUE}[{number}/{total}]{Style.RESET_ALL} {message}...")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def header():
|
|
47
|
+
"""Print the welcome banner."""
|
|
48
|
+
_safe_print(f"""
|
|
49
|
+
{Fore.CYAN}{"=" * 60}
|
|
50
|
+
projectdevsetup - Zenith Open Source Projects
|
|
51
|
+
Automatic Developer Environment Setup for Beginners
|
|
52
|
+
{"=" * 60}{Style.RESET_ALL}
|
|
53
|
+
""")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def already_installed(tool: str):
|
|
57
|
+
"""Tell user a tool is already installed - skip it."""
|
|
58
|
+
_safe_print(
|
|
59
|
+
f"{Fore.GREEN}[OK] {tool} is already installed. Skipping.{Style.RESET_ALL}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def ask(prompt: str) -> str:
|
|
64
|
+
"""Prompt the user for input with consistent styling."""
|
|
65
|
+
return input(f"{Fore.YELLOW}>> {prompt}{Style.RESET_ALL} ")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def divider():
|
|
69
|
+
_safe_print(f"{Fore.CYAN}{'-' * 60}{Style.RESET_ALL}")
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import platform
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SystemInfo:
|
|
17
|
+
os_name: str
|
|
18
|
+
os_version: str
|
|
19
|
+
architecture: str
|
|
20
|
+
distro: str
|
|
21
|
+
distro_version: str
|
|
22
|
+
package_manager: str
|
|
23
|
+
is_64bit: bool
|
|
24
|
+
is_arm: bool
|
|
25
|
+
has_sudo: bool
|
|
26
|
+
has_display: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def detect_system() -> SystemInfo:
|
|
30
|
+
"""Detect all system information needed for setup decisions."""
|
|
31
|
+
raw_os = platform.system().lower()
|
|
32
|
+
arch = platform.machine().lower()
|
|
33
|
+
is_arm = arch in ("arm64", "aarch64", "armv7l", "armv6l")
|
|
34
|
+
is_64bit = sys.maxsize > 2**32
|
|
35
|
+
|
|
36
|
+
os_name = ""
|
|
37
|
+
os_version = ""
|
|
38
|
+
distro = ""
|
|
39
|
+
distro_version = ""
|
|
40
|
+
package_manager = ""
|
|
41
|
+
has_sudo = False
|
|
42
|
+
has_display = True
|
|
43
|
+
|
|
44
|
+
if raw_os == "windows":
|
|
45
|
+
os_name = "windows"
|
|
46
|
+
os_version = platform.version()
|
|
47
|
+
package_manager = _detect_windows_package_manager()
|
|
48
|
+
|
|
49
|
+
elif raw_os == "linux":
|
|
50
|
+
os_name = "linux"
|
|
51
|
+
distro, distro_version = _detect_linux_distro()
|
|
52
|
+
package_manager = _detect_linux_package_manager()
|
|
53
|
+
has_sudo = _check_sudo()
|
|
54
|
+
has_display = _check_display()
|
|
55
|
+
|
|
56
|
+
elif raw_os == "darwin":
|
|
57
|
+
os_name = "mac"
|
|
58
|
+
os_version = platform.mac_ver()[0]
|
|
59
|
+
package_manager = "brew" if _command_exists("brew") else "none"
|
|
60
|
+
has_sudo = _check_sudo()
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
os_name = "unknown"
|
|
64
|
+
|
|
65
|
+
return SystemInfo(
|
|
66
|
+
os_name=os_name,
|
|
67
|
+
os_version=os_version,
|
|
68
|
+
architecture=arch,
|
|
69
|
+
distro=distro,
|
|
70
|
+
distro_version=distro_version,
|
|
71
|
+
package_manager=package_manager,
|
|
72
|
+
is_64bit=is_64bit,
|
|
73
|
+
is_arm=is_arm,
|
|
74
|
+
has_sudo=has_sudo,
|
|
75
|
+
has_display=has_display,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _detect_linux_distro() -> tuple:
|
|
80
|
+
"""Detect Linux distribution name and version."""
|
|
81
|
+
try:
|
|
82
|
+
with open("/etc/os-release") as f:
|
|
83
|
+
lines = f.read().lower()
|
|
84
|
+
name = ""
|
|
85
|
+
version = ""
|
|
86
|
+
for line in lines.splitlines():
|
|
87
|
+
if line.startswith("id="):
|
|
88
|
+
name = line.split("=")[1].strip().strip('"')
|
|
89
|
+
if line.startswith("version_id="):
|
|
90
|
+
version = line.split("=")[1].strip().strip('"')
|
|
91
|
+
return name, version
|
|
92
|
+
except Exception:
|
|
93
|
+
return "unknown", ""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _detect_linux_package_manager() -> str:
|
|
97
|
+
"""Return the available package manager on Linux."""
|
|
98
|
+
managers = ["apt", "pacman", "dnf", "yum", "zypper", "apk"]
|
|
99
|
+
for mgr in managers:
|
|
100
|
+
if _command_exists(mgr):
|
|
101
|
+
return mgr
|
|
102
|
+
return "none"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _detect_windows_package_manager() -> str:
|
|
106
|
+
"""Return available package manager on Windows."""
|
|
107
|
+
if _command_exists("winget"):
|
|
108
|
+
return "winget"
|
|
109
|
+
if _command_exists("choco"):
|
|
110
|
+
return "choco"
|
|
111
|
+
return "none"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _command_exists(cmd: str) -> bool:
|
|
115
|
+
"""Check if a shell command exists on this system."""
|
|
116
|
+
try:
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
["where", cmd] if platform.system() == "Windows" else ["which", cmd],
|
|
119
|
+
capture_output=True,
|
|
120
|
+
text=True,
|
|
121
|
+
)
|
|
122
|
+
return result.returncode == 0
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _check_sudo() -> bool:
|
|
128
|
+
"""Check if sudo is available on Linux/Mac."""
|
|
129
|
+
try:
|
|
130
|
+
result = subprocess.run(["sudo", "-n", "true"], capture_output=True, text=True)
|
|
131
|
+
return result.returncode == 0
|
|
132
|
+
except Exception:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _check_display() -> bool:
|
|
137
|
+
"""Check if a display is available (for headless Linux detection)."""
|
|
138
|
+
return bool(
|
|
139
|
+
os.environ.get("DISPLAY")
|
|
140
|
+
or os.environ.get("WAYLAND_DISPLAY")
|
|
141
|
+
or platform.system() != "Linux"
|
|
142
|
+
)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@project projectdevsetup
|
|
3
|
+
@org Zenith Open Source Projects
|
|
4
|
+
@license MIT License
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import platform
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from projectdevsetup.utils.logger import info, success, warning, error
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_venv(project_dir: Path) -> bool:
|
|
14
|
+
"""
|
|
15
|
+
Create a Python virtual environment inside the project directory.
|
|
16
|
+
Shows beginner-friendly instructions on how to use it.
|
|
17
|
+
Returns True on success.
|
|
18
|
+
"""
|
|
19
|
+
venv_path = project_dir / ".venv"
|
|
20
|
+
|
|
21
|
+
if venv_path.exists():
|
|
22
|
+
success("Virtual environment already exists. Skipping.")
|
|
23
|
+
_show_venv_instructions(venv_path)
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
info("Creating Python virtual environment (.venv)...")
|
|
27
|
+
try:
|
|
28
|
+
result = subprocess.run(
|
|
29
|
+
["python3", "-m", "venv", str(venv_path)],
|
|
30
|
+
capture_output=True,
|
|
31
|
+
text=True,
|
|
32
|
+
timeout=60,
|
|
33
|
+
)
|
|
34
|
+
if result.returncode != 0:
|
|
35
|
+
result = subprocess.run(
|
|
36
|
+
["python", "-m", "venv", str(venv_path)],
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=60,
|
|
40
|
+
)
|
|
41
|
+
if result.returncode == 0:
|
|
42
|
+
success("Virtual environment created!")
|
|
43
|
+
_create_requirements_txt(project_dir)
|
|
44
|
+
_show_venv_instructions(venv_path)
|
|
45
|
+
return True
|
|
46
|
+
else:
|
|
47
|
+
error(
|
|
48
|
+
"Could not create virtual environment. "
|
|
49
|
+
"Make sure Python is properly installed."
|
|
50
|
+
)
|
|
51
|
+
return False
|
|
52
|
+
except Exception:
|
|
53
|
+
error(
|
|
54
|
+
"Failed to create virtual environment. "
|
|
55
|
+
"Please make sure python3-venv is installed."
|
|
56
|
+
)
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _create_requirements_txt(project_dir: Path):
|
|
61
|
+
"""Create an empty requirements.txt with instructions."""
|
|
62
|
+
req_file = project_dir / "requirements.txt"
|
|
63
|
+
if not req_file.exists():
|
|
64
|
+
req_file.write_text(
|
|
65
|
+
"# Add your Python packages here, one per line.\n"
|
|
66
|
+
"# Example:\n"
|
|
67
|
+
"# requests\n"
|
|
68
|
+
"# numpy\n"
|
|
69
|
+
"#\n"
|
|
70
|
+
"# To install all packages, run:\n"
|
|
71
|
+
"# pip install -r requirements.txt\n"
|
|
72
|
+
)
|
|
73
|
+
success("requirements.txt created.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _show_venv_instructions(venv_path: Path):
|
|
77
|
+
"""Show beginner-friendly venv activation instructions."""
|
|
78
|
+
os_name = platform.system().lower()
|
|
79
|
+
|
|
80
|
+
if os_name == "windows":
|
|
81
|
+
activate_cmd = f".venv\\Scripts\\activate"
|
|
82
|
+
else:
|
|
83
|
+
activate_cmd = "source .venv/bin/activate"
|
|
84
|
+
|
|
85
|
+
print(f"""
|
|
86
|
+
┌─────────────────────────────────────────────┐
|
|
87
|
+
│ How to use your virtual environment: │
|
|
88
|
+
│ │
|
|
89
|
+
│ 1. Open your terminal in this folder │
|
|
90
|
+
│ 2. Type: {activate_cmd:<35} │
|
|
91
|
+
│ 3. You will see (.venv) appear │
|
|
92
|
+
│ 4. Install packages: pip install <name> │
|
|
93
|
+
│ 5. To deactivate: type deactivate │
|
|
94
|
+
└─────────────────────────────────────────────┘
|
|
95
|
+
""")
|