Skip to content

Commit 0e2c156

Browse files
committed
init v1.0.0
0 parents  commit 0e2c156

File tree

14 files changed

+824
-0
lines changed

14 files changed

+824
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Build and Scan
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
jobs:
12+
build:
13+
runs-on: windows-latest
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.13'
23+
24+
- name: Install dependencies
25+
run: |
26+
python -m pip install --upgrade pip
27+
pip install -r requirements.txt
28+
pip install pyinstaller
29+
30+
- name: Build EXE
31+
run: |
32+
python -m PyInstaller --onefile --windowed --icon=python.ico --add-data "python.ico;." --add-data "python.png;." --name="PythonProjectMngr" app.py
33+
env:
34+
PYTHONIOENCODING: utf-8
35+
36+
- name: Upload artifact
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: PythonProjectMngr
40+
path: dist/PythonProjectMngr.exe
41+
42+
- name: Scan with VirusTotal
43+
if: success() && env.VIRUSTOTAL_API_KEY != ''
44+
uses: crazy-max/ghaction-virustotal@v4.0.0
45+
with:
46+
vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
47+
files: dist/PythonProjectMngr.exe

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*/__pycache__/
2+
config.json
3+
/build/
4+
/dist/
5+
*.spec

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 King Triton
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.

app.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
PythonProjectMngr - Главный файл приложения
3+
"""
4+
import sys
5+
from PyQt6.QtWidgets import QApplication
6+
from core.manager import ProjectManager
7+
from ui.tray import TrayIcon
8+
9+
10+
def main():
11+
app = QApplication(sys.argv)
12+
app.setQuitOnLastWindowClosed(False)
13+
14+
manager = ProjectManager()
15+
tray = TrayIcon(manager)
16+
tray.show()
17+
18+
sys.exit(app.exec())
19+
20+
21+
if __name__ == "__main__":
22+
main()

core/__init__.py

Whitespace-only changes.

core/manager.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
Менеджер проектов - основная бизнес-логика
3+
"""
4+
import os
5+
import json
6+
import shutil
7+
from pathlib import Path
8+
9+
10+
class ProjectManager:
11+
def __init__(self):
12+
user = os.getlogin()
13+
self.app_folder = Path(f"C:/Users/{user}/PythonProjectMngr")
14+
self.app_folder.mkdir(parents=True, exist_ok=True)
15+
16+
self.config_file = self.app_folder / "settings.mngr"
17+
self.load_config()
18+
19+
def get_default_root(self):
20+
default = self.app_folder / "Projects"
21+
default.mkdir(parents=True, exist_ok=True)
22+
return str(default)
23+
24+
def load_config(self):
25+
default_root = self.get_default_root()
26+
if self.config_file.exists():
27+
try:
28+
with open(self.config_file, "r", encoding="utf-8") as f:
29+
config = json.load(f)
30+
self.root_path = config.get("root_path", default_root)
31+
self.open_after_create = config.get("open_after_create", True)
32+
except:
33+
self.root_path = default_root
34+
self.open_after_create = True
35+
self.save_config()
36+
else:
37+
self.root_path = default_root
38+
self.open_after_create = True
39+
self.save_config()
40+
41+
def save_config(self):
42+
with open(self.config_file, "w", encoding="utf-8") as f:
43+
json.dump({
44+
"root_path": self.root_path,
45+
"open_after_create": self.open_after_create
46+
}, f, ensure_ascii=False, indent=4)
47+
48+
def ensure_root_exists(self):
49+
"""Убедиться, что корневая папка существует"""
50+
os.makedirs(self.root_path, exist_ok=True)
51+
52+
def create_project(self, project_name):
53+
"""
54+
Создание нового проекта
55+
Returns: (success: bool, message: str, project_path: str)
56+
"""
57+
if not project_name or not project_name.strip():
58+
return False, "Введите название проекта!", None
59+
60+
name = project_name.strip()
61+
62+
# Проверка на недопустимые символы
63+
invalid_chars = '<>:"/\\|?*'
64+
if any(char in name for char in invalid_chars):
65+
return False, "Название содержит недопустимые символы!", None
66+
67+
self.ensure_root_exists()
68+
project_path = os.path.join(self.root_path, name)
69+
70+
if os.path.exists(project_path):
71+
return False, f"Проект '{name}' уже существует!", None
72+
73+
try:
74+
os.makedirs(project_path)
75+
app_file = os.path.join(project_path, "app.py")
76+
with open(app_file, 'w', encoding='utf-8') as f:
77+
f.write("") # Создаём пустой файл
78+
79+
return True, f"Проект '{name}' создан!", project_path
80+
except Exception as e:
81+
return False, f"Не удалось создать проект:\n{str(e)}", None
82+
83+
def get_projects(self):
84+
"""Получение списка проектов"""
85+
self.ensure_root_exists()
86+
try:
87+
return sorted([
88+
d for d in os.listdir(self.root_path)
89+
if os.path.isdir(os.path.join(self.root_path, d))
90+
])
91+
except Exception:
92+
return []
93+
94+
def delete_project(self, project_name):
95+
"""Удаление проекта"""
96+
if not project_name:
97+
return False, "Проект не выбран!"
98+
99+
project_path = os.path.join(self.root_path, project_name)
100+
101+
if not os.path.exists(project_path):
102+
return False, f"Проект '{project_name}' не найден!"
103+
104+
try:
105+
shutil.rmtree(project_path)
106+
return True, f"Проект '{project_name}' удалён!"
107+
except Exception as e:
108+
return False, f"Не удалось удалить проект:\n{str(e)}"
109+
110+
def open_projects_folder(self):
111+
"""Открытие папки с проектами"""
112+
self.ensure_root_exists()
113+
os.startfile(self.root_path)
114+
115+
def open_project_folder(self, project_path):
116+
"""Открытие конкретной папки проекта"""
117+
if os.path.exists(project_path):
118+
os.startfile(project_path)
119+
120+
def change_root_path(self, new_path, move_projects=True):
121+
"""Изменение корневой папки"""
122+
if not new_path:
123+
return False, "Путь не указан!"
124+
125+
old_path = self.root_path
126+
127+
if old_path == new_path:
128+
return True, "Путь не изменился"
129+
130+
try:
131+
# Создаём новую папку если не существует
132+
os.makedirs(new_path, exist_ok=True)
133+
134+
# Переносим проекты если нужно
135+
if move_projects and os.path.exists(old_path):
136+
projects = self.get_projects()
137+
138+
for project in projects:
139+
old_project_path = os.path.join(old_path, project)
140+
new_project_path = os.path.join(new_path, project)
141+
142+
if not os.path.exists(new_project_path):
143+
shutil.move(old_project_path, new_project_path)
144+
145+
self.root_path = new_path
146+
self.save_config()
147+
148+
return True, "Настройки сохранены и проекты перенесены!"
149+
except PermissionError:
150+
return False, "Отказано в доступе. Выбери другую папку или запусти программу от имени администратора."
151+
except Exception as e:
152+
return False, f"Ошибка при изменении пути:\n{str(e)}"

core/utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Вспомогательные функции и утилиты приложения
3+
"""
4+
import sys
5+
from pathlib import Path
6+
from PyQt6.QtGui import QIcon, QDesktopServices
7+
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout
8+
from PyQt6.QtCore import Qt, QUrl
9+
10+
def set_window_icon(window: QWidget, icon_name="python.ico"):
11+
if getattr(sys, 'frozen', False):
12+
base_path = Path(sys._MEIPASS)
13+
else:
14+
base_path = Path(__file__).parent.parent
15+
16+
icon_path = base_path / icon_name
17+
window.setWindowIcon(QIcon(str(icon_path)))
18+
19+
def add_footer_label(window: QWidget, text=None):
20+
if text is None:
21+
text = (
22+
'Создал <a href="https://github.com/king-tri-ton" '
23+
'style="color: #2980b9; text-decoration: none;">King Triton</a> '
24+
'для Python сообщества | '
25+
'<a href="https://github.com/king-tri-ton/PythonProjectMngr" '
26+
'style="color: #2980b9; text-decoration: none;">PythonProjectMngr</a>'
27+
)
28+
29+
layout = window.layout()
30+
if layout is None:
31+
return
32+
33+
label = QLabel(text)
34+
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
35+
label.setTextInteractionFlags(Qt.TextInteractionFlag.TextBrowserInteraction)
36+
label.setOpenExternalLinks(True)
37+
label.setStyleSheet("font-size: 10pt; color: gray; margin-top: 10px; margin-bottom: 5px;")
38+
39+
layout.addWidget(label)
40+
41+
42+
def center_window(window):
43+
"""Центрирование окна на экране"""
44+
screen = window.screen().geometry()
45+
x = (screen.width() - window.width()) // 2
46+
y = (screen.height() - window.height()) // 2
47+
window.move(x, y)

python.ico

122 KB
Binary file not shown.

python.png

26.9 KB
Loading

0 commit comments

Comments
 (0)