encapsulation of script parsing logic

This commit is contained in:
Sergey Palcheh
2025-08-15 10:48:43 +06:00
parent 4e1c3e787d
commit 3bfdf5c01a

View File

@@ -525,7 +525,7 @@ class WinetricksManagerDialog(QDialog):
layout.addWidget(search_edit)
list_widget = QListWidget()
list_widget.itemChanged.connect(self._update_ui_state)
list_widget.itemChanged.connect(self._on_item_changed)
list_widget.currentItemChanged.connect(self._update_ui_state)
layout.addWidget(list_widget)
@@ -651,18 +651,8 @@ class WinetricksManagerDialog(QDialog):
item = QListWidgetItem(item_text)
item.setData(Qt.UserRole, name)
item.setFont(QFont("DejaVu Sans Mono", 10))
if is_checked:
# Если компонент уже установлен, делаем его неинтерактивным,
# так как удаление не поддерживается. Переустановка - через отдельную кнопку.
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked)
item.setToolTip("Этот компонент уже установлен. Для переустановки выделите его и нажмите кнопку 'Переустановить'.")
else:
# Для неустановленных компонентов разрешаем установку через чекбокс.
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Unchecked)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked if is_checked else Qt.Unchecked)
list_widget.addItem(item)
self.initial_states[name] = is_checked
@@ -728,6 +718,21 @@ class WinetricksManagerDialog(QDialog):
self.status_label.setText("Готово.")
self._update_ui_state()
def _on_item_changed(self, item):
"""Обрабатывает изменение состояния чекбокса, предотвращая снятие галочки с установленных."""
name = item.data(Qt.UserRole)
# Если компонент был изначально установлен и пользователь пытается его снять
if name in self.initial_states and self.initial_states.get(name) is True:
if item.checkState() == Qt.Unchecked:
# Блокируем сигналы, чтобы избежать рекурсии, и возвращаем галочку на место.
list_widget = item.listWidget()
if list_widget:
list_widget.blockSignals(True)
item.setCheckState(Qt.Checked)
if list_widget:
list_widget.blockSignals(False)
self._update_ui_state()
def _update_ui_state(self, *args):
"""Централизованно обновляет состояние кнопок 'Применить' и 'Переустановить'."""
# 1. Проверяем, есть ли изменения в чекбоксах (установка новых или снятие галочек с новых)
@@ -919,6 +924,93 @@ class WinetricksManagerDialog(QDialog):
self.log_output.append(message)
self.log_output.moveCursor(QTextCursor.End)
class ScriptParser:
"""Утилитарный класс для парсинга информации из скриптов установки."""
@staticmethod
def extract_icons_from_script(script_path):
"""
Извлекает иконку для скрипта.
Сначала ищет переменную 'export PROG_ICON=', если не находит,
то ищет все вызовы 'create_desktop' и берет иконки из третьего аргумента.
Возвращает список имен иконок.
"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 1. Приоритет у PROG_ICON
for line in lines:
if line.strip().startswith('export PROG_ICON='):
icon_name = line.split('=', 1)[1].strip().strip('"\'')
if icon_name:
return [icon_name]
# 2. Если PROG_ICON не найден, ищем все вызовы create_desktop
icon_names = []
for line in lines:
line = line.strip()
# Пропускаем закомментированные строки и пустые строки
if not line or line.startswith('#'):
continue
if 'create_desktop' in line:
try:
parts = shlex.split(line)
# Ищем все вхождения, а не только первое
for i, part in enumerate(parts):
if part == 'create_desktop':
if len(parts) > i + 3:
icon_name = parts[i + 3]
if icon_name:
icon_names.append(icon_name)
except (ValueError, IndexError):
continue
return icon_names
except Exception as e:
print(f"Ошибка чтения файла для извлечения иконки: {str(e)}")
return []
@staticmethod
def extract_prog_name_from_script(script_path):
"""Извлекает имя программы из строки PROG_NAME= в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
if line.strip().startswith(('export PROG_NAME=', 'PROG_NAME=')):
name = line.split('=', 1)[1].strip().strip('"\'')
if name:
return name
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_NAME: {str(e)}")
return None
@staticmethod
def extract_prog_url_from_script(script_path):
"""Извлекает URL из строки export PROG_URL= в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('export PROG_URL='):
return line.replace('export PROG_URL=', '').strip().strip('"\'')
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
return None
@staticmethod
def extract_info_ru(script_path):
"""Извлекает информацию из строки # info_ru: в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('# info_ru:'):
return line.replace('# info_ru:', '').strip()
return "Описание отсутствует"
except Exception as e:
return f"Ошибка чтения файла: {str(e)}"
class WineHelperGUI(QMainWindow):
def __init__(self):
super().__init__()
@@ -1235,77 +1327,6 @@ class WineHelperGUI(QMainWindow):
if file_path:
self.install_path_edit.setText(file_path)
def extract_icons_from_script(self, script_path):
"""
Извлекает иконку для скрипта.
Сначала ищет переменную 'export PROG_ICON=', если не находит,
то ищет все вызовы 'create_desktop' и берет иконки из третьего аргумента.
Возвращает список имен иконок.
"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 1. Приоритет у PROG_ICON
for line in lines:
if line.strip().startswith('export PROG_ICON='):
icon_name = line.split('=', 1)[1].strip().strip('"\'')
if icon_name:
return [icon_name]
# 2. Если PROG_ICON не найден, ищем все вызовы create_desktop
icon_names = []
for line in lines:
line = line.strip()
# Пропускаем закомментированные строки и пустые строки
if not line or line.startswith('#'):
continue
if 'create_desktop' in line:
try:
parts = shlex.split(line)
# Ищем все вхождения, а не только первое
for i, part in enumerate(parts):
if part == 'create_desktop':
if len(parts) > i + 3:
icon_name = parts[i + 3]
if icon_name:
icon_names.append(icon_name)
except (ValueError, IndexError):
continue
return icon_names
except Exception as e:
print(f"Ошибка чтения файла для извлечения иконки: {str(e)}")
return []
def extract_prog_name_from_script(self, script_path):
"""Извлекает имя программы из строки PROG_NAME= в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
# Ищем строку, которая начинается с PROG_NAME= или export PROG_NAME=
if line.strip().startswith(('export PROG_NAME=', 'PROG_NAME=')):
# Отделяем имя переменной от значения и убираем кавычки
name = line.split('=', 1)[1].strip().strip('"\'')
if name:
return name
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_NAME: {str(e)}")
return None
def extract_prog_url_from_script(self, script_path):
"""Извлекает URL из строки export PROG_URL= в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('export PROG_URL='):
return line.replace('export PROG_URL=', '').strip().strip('"\'')
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
return None
def _start_icon_fade_animation(self, button):
"""Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation."""
if button not in self.icon_animators:
@@ -1426,13 +1447,13 @@ class WineHelperGUI(QMainWindow):
button_index = 0
for script in scripts_list:
script_path = os.path.join(Var.DATA_PATH, script_folder, script)
prog_name = self.extract_prog_name_from_script(script_path)
prog_name = ScriptParser.extract_prog_name_from_script(script_path)
# Создаем кнопку, только если для скрипта указано имя программы
if not prog_name:
continue
icon_names = self.extract_icons_from_script(script_path)
icon_names = ScriptParser.extract_icons_from_script(script_path)
icon_paths = [os.path.join(Var.DATA_PATH, "image", f"{name}.png") for name in icon_names]
btn = self._create_app_button(prog_name, icon_paths, self.BUTTON_LIST_STYLE)
@@ -2224,17 +2245,6 @@ class WineHelperGUI(QMainWindow):
self.installed_search_edit.text(), self.installed_buttons, self.installed_scroll_layout
)
def extract_info_ru(self, script_path):
"""Извлекает информацию из строки # info_ru: в скрипте"""
try:
with open(script_path, 'r', encoding='utf-8') as f:
for line in f:
if line.startswith('# info_ru:'):
return line.replace('# info_ru:', '').strip()
return "Описание отсутствует"
except Exception as e:
return f"Ошибка чтения файла: {str(e)}"
def show_script_info(self, script_name, button_widget):
"""Показывает информацию о выбранном скрипте"""
self._set_active_button(button_widget)
@@ -2264,10 +2274,10 @@ class WineHelperGUI(QMainWindow):
QTimer.singleShot(0, lambda: scroll_area.ensureWidgetVisible(frame))
# Обновляем информацию в правой панели
description = self.extract_info_ru(script_path)
icon_names = self.extract_icons_from_script(script_path)
prog_name = self.extract_prog_name_from_script(script_path)
prog_url = self.extract_prog_url_from_script(script_path)
description = ScriptParser.extract_info_ru(script_path)
icon_names = ScriptParser.extract_icons_from_script(script_path)
prog_name = ScriptParser.extract_prog_name_from_script(script_path)
prog_url = ScriptParser.extract_prog_url_from_script(script_path)
display_name = prog_name if prog_name else script_name
self.current_display_name = display_name