import sys import os import subprocess import xml.etree.ElementTree as ET from generateVars import map_type_to_pt, get_iq_define, type_map from enum import IntEnum from PySide6.QtWidgets import ( QApplication, QWidget, QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton, QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit ) from PySide6.QtGui import QTextCursor from PySide6.QtCore import Qt, QProcess class rows(IntEnum): include = 0 name = 1 type = 2 pt_type = 3 iq_type = 4 ret_type = 5 short_name = 6 # 1. Парсим vars.xml def make_absolute_path(path, base_path): if not os.path.isabs(path): return os.path.abspath(os.path.join(base_path, path)) return os.path.abspath(path) def parse_vars(filename): tree = ET.parse(filename) root = tree.getroot() vars_list = [] variables_elem = root.find('variables') if variables_elem is not None: for var in variables_elem.findall('var'): name = var.attrib.get('name', '') vars_list.append({ 'name': name, 'enable': var.findtext('enable', 'false'), 'shortname': var.findtext('shortname', name), 'pt_type': var.findtext('pt_type', 'pt_unknown'), 'iq_type': var.findtext('iq_type', 'iq_none'), 'return_type': var.findtext('return_type', 'int'), 'type': var.findtext('type', 'unknown'), 'file': var.findtext('file', ''), 'extern': var.findtext('extern', 'false') == 'true', 'static': var.findtext('static', 'false') == 'true', }) return vars_list # 2. Парсим structSup.xml def parse_structs(filename): tree = ET.parse(filename) root = tree.getroot() structs = {} typedef_map = {} # --- Считываем структуры --- structs_elem = root.find('structs') if structs_elem is not None: for struct in structs_elem.findall('struct'): name = struct.attrib['name'] fields = {} for field in struct.findall('field'): fname = field.attrib.get('name') ftype = field.attrib.get('type') if fname and ftype: fields[fname] = ftype structs[name] = fields # --- Считываем typedef-ы --- typedefs_elem = root.find('typedefs') if typedefs_elem is not None: for typedef in typedefs_elem.findall('typedef'): name = typedef.attrib.get('name') target_type = typedef.attrib.get('type') if name and target_type: typedef_map[name.strip()] = target_type.strip() return structs, typedef_map class ProcessOutputWindow(QWidget): def __init__(self, command, args): super().__init__() self.setWindowTitle("Поиск переменных...") self.resize(600, 400) self.layout = QVBoxLayout(self) self.output_edit = QTextEdit() self.output_edit.setReadOnly(True) self.layout.addWidget(self.output_edit) self.btn_close = QPushButton("Закрыть") self.btn_close.setEnabled(False) self.btn_close.clicked.connect(self.close) self.layout.addWidget(self.btn_close) self.process = QProcess(self) self.process.setProcessChannelMode(QProcess.MergedChannels) self.process.readyReadStandardOutput.connect(self.handle_stdout) self.process.finished.connect(self.process_finished) self.process.start(command, args) def handle_stdout(self): data = self.process.readAllStandardOutput() text = data.data().decode('utf-8') self.output_edit.append(text) def process_finished(self): self.output_edit.append("\n--- Процесс завершён ---") self.btn_close.setEnabled(True) # 3. UI: таблица с переменными class VarEditor(QWidget): def __init__(self): super().__init__() self.vars_list = [] self.structs = {} self.typedef_map = {} self.structs_xml_path = None # сюда запишем путь из self.initUI() def initUI(self): self.setWindowTitle("Variable Editor") # --- Поля ввода пути проекта и XML --- # XML Output xml_layout = QHBoxLayout() xml_layout.addWidget(QLabel("XML Output:")) self.xml_output_edit = QLineEdit() xml_layout.addWidget(self.xml_output_edit) self.xml_output_edit.returnPressed.connect(self.read_xml_file) btn_xml_browse = QPushButton("...") btn_xml_browse.setFixedWidth(30) xml_layout.addWidget(btn_xml_browse) btn_xml_browse.clicked.connect(self.browse_xml_output) # Project Path proj_layout = QHBoxLayout() proj_layout.addWidget(QLabel("Project Path:")) self.proj_path_edit = QLineEdit() proj_layout.addWidget(self.proj_path_edit) btn_proj_browse = QPushButton("...") btn_proj_browse.setFixedWidth(30) proj_layout.addWidget(btn_proj_browse) btn_proj_browse.clicked.connect(self.browse_proj_path) # Makefile Path makefile_layout = QHBoxLayout() makefile_layout.addWidget(QLabel("Makefile Path (relative path):")) self.makefile_edit = QLineEdit() makefile_layout.addWidget(self.makefile_edit) btn_makefile_browse = QPushButton("...") btn_makefile_browse.setFixedWidth(30) makefile_layout.addWidget(btn_makefile_browse) btn_makefile_browse.clicked.connect(self.browse_makefile) # Source Output File/Directory source_output_layout = QHBoxLayout() source_output_layout.addWidget(QLabel("Source Output File:")) self.source_output_edit = QLineEdit() source_output_layout.addWidget(self.source_output_edit) btn_source_output_browse = QPushButton("...") btn_source_output_browse.setFixedWidth(30) source_output_layout.addWidget(btn_source_output_browse) btn_source_output_browse.clicked.connect(self.browse_source_output) self.btn_update_vars = QPushButton("Обновить данные о переменных") self.btn_update_vars.clicked.connect(self.update_vars_data) # Таблица переменных self.table = QTableWidget(len(self.vars_list), 7) self.table.setHorizontalHeaderLabels([ 'Include', 'Name', 'Origin Type', 'Pointer Type', 'IQ Type', 'Return Type', 'Short Name' ]) self.table.setEditTriggers(QAbstractItemView.AllEditTriggers) # Кнопка сохранения btn_save = QPushButton("Build") btn_save.clicked.connect(self.save_build) # Основной layout layout = QVBoxLayout() layout.addLayout(xml_layout) layout.addLayout(proj_layout) layout.addLayout(makefile_layout) layout.addWidget(self.btn_update_vars) layout.addWidget(self.table) layout.addWidget(btn_save) layout.addLayout(source_output_layout) self.setLayout(layout) def update_vars_data(self): proj_path = self.proj_path_edit.text().strip() xml_path = self.xml_output_edit.text().strip() proj_path = os.path.abspath(self.proj_path_edit.text().strip()) makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path) if not proj_path or not xml_path: QMessageBox.warning(self, "Ошибка", "Пожалуйста, укажите пути проекта и XML.") return if not os.path.isfile(makefile_path): QMessageBox.warning(self, "Ошибка", f"Makefile не найден по пути:\n{makefile_path}") return scanvars_exe = "scanVars.exe" args = [proj_path, makefile_path, xml_path] # Создаем и показываем окно с выводом процесса self.proc_win = ProcessOutputWindow(scanvars_exe, args) self.proc_win.show() # Можно подписаться на сигнал завершения процесса, если хочешь обновить UI после self.proc_win.process.finished.connect(lambda exitCode, exitStatus: self.after_scanvars_finished(exitCode, exitStatus)) def save_build(self): vars_out = [] for row in range(self.table.rowCount()): include_cb = self.table.cellWidget(row, rows.include) if not include_cb.isChecked(): continue name_edit = self.table.cellWidget(row, rows.name) pt_type_combo = self.table.cellWidget(row, rows.pt_type) iq_combo = self.table.cellWidget(row, rows.iq_type) ret_combo = self.table.cellWidget(row, rows.ret_type) short_name_edit = self.table.cellWidget(row, rows.short_name) var_data = { 'name': name_edit.text(), 'type': 'pt_' + pt_type_combo.currentText(), 'iq_type': iq_combo.currentText(), 'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int', 'short_name': short_name_edit.text(), } vars_out.append(var_data) # Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои) proj_path = self.proj_path_edit.text().strip() xml_path = self.xml_output_edit.text().strip() output_dir_c_file = self.source_output_edit.text().strip() # Путь к твоему exe, например exe_path = r"generateVars.exe" # Формируем список аргументов cmd = [exe_path, proj_path, xml_path, output_dir_c_file] try: # Запускаем exe и ждём завершения result = subprocess.run(cmd, capture_output=True, text=True, check=True) except subprocess.CalledProcessError as e: print("Error calling script:", e) print("stderr:", e.stderr) def browse_proj_path(self): dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта") if dir_path: self.proj_path_edit.setText(dir_path) def read_xml_file(self): file_path = self.xml_output_edit.text().strip() if file_path and not os.path.isfile(file_path): return self.vars_list = parse_vars(file_path) try: tree = ET.parse(file_path) root = tree.getroot() proj_path = self.proj_path_edit.text().strip() if not proj_path: # Если в поле ничего нет, пробуем взять из XML proj_path_from_xml = root.attrib.get('proj_path', '').strip() if proj_path_from_xml and os.path.isdir(proj_path_from_xml): proj_path = proj_path_from_xml self.proj_path_edit.setText(proj_path_from_xml) else: QMessageBox.warning( self, "Внимание", "Путь к проекту (proj_path) не найден или не существует.\n" "Пожалуйста, укажите его вручную в поле 'Project Path'." ) else: if not os.path.isdir(proj_path): QMessageBox.warning( self, "Внимание", f"Указанный путь к проекту не существует:\n{proj_path}\n" "Пожалуйста, исправьте путь в поле 'Project Path'." ) # --- makefile_path из атрибута --- makefile_path = root.attrib.get('makefile_path', '').strip() makefile_path_full = make_absolute_path(makefile_path, proj_path) if makefile_path_full and os.path.isfile(makefile_path_full): self.makefile_edit.setText(makefile_path) # --- structs_path из атрибута --- structs_path = root.attrib.get('structs_path', '').strip() structs_path_full = make_absolute_path(structs_path, proj_path) if structs_path_full and os.path.isfile(structs_path_full): self.structs_xml_path = structs_path_full self.structs, self.typedef_map = parse_structs(structs_path_full) else: self.structs_xml_path = None self.update_table() except Exception as e: QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}") def browse_xml_output(self): file_path, _ = QFileDialog.getSaveFileName( self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)" ) self.xml_output_edit.setText(file_path) self.read_xml_file() def browse_xml_struct(self): file_path, _ = QFileDialog.getSaveFileName(self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)") if file_path: self.xml_output_edit.setText(file_path) if os.path.isfile(file_path): self.structs, self.typedef_map = parse_structs(file_path) def browse_makefile(self): file_path, _ = QFileDialog.getOpenFileName( self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)" ) if file_path: self.makefile_edit.setText(file_path) def browse_source_output(self): dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c") if dir_path: self.source_output_edit.setText(dir_path) def after_scanvars_finished(self, exitCode, exitStatus): xml_path = self.xml_output_edit.text().strip() if not os.path.isfile(xml_path): QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}") return try: # Читаем структуры, если задан путь if self.structs_xml_path and os.path.isfile(self.structs_xml_path): try: self.structs, self.typedef_map = parse_structs(self.structs_xml_path) # При необходимости обновите UI или сделайте что-то с self.structs except Exception as e: QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_xml_path}:\n{e}") self.vars_list = parse_vars(xml_path) self.update_table() except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}") def update_table(self): self.type_options = list(dict.fromkeys(type_map.values())) self.display_type_options = [t.replace('pt_', '') for t in self.type_options] iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)] self.table.setRowCount(len(self.vars_list)) for row, var in enumerate(self.vars_list): cb = QCheckBox() enable_str = var.get('enable', 'false') cb.setChecked(enable_str.lower() == 'true') self.table.setCellWidget(row, rows.include, cb) name_edit = QLineEdit(var['name']) if var['type'] in self.structs: completer = QCompleter(self.structs[var['type']].keys()) completer.setCaseSensitivity(Qt.CaseInsensitive) name_edit.setCompleter(completer) self.table.setCellWidget(row, rows.name, name_edit) # Type (origin) origin_type = var.get('type', '').strip() origin_item = QTableWidgetItem(origin_type) origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # read-only self.table.setItem(row, rows.type, origin_item) pt_type_combo = QComboBox() pt_type_combo.addItems(self.display_type_options) internal_type = map_type_to_pt(var['type'], var['name'], self.typedef_map) display_type = internal_type.replace('pt_', '') if display_type in self.display_type_options: pt_type_combo.setCurrentText(display_type) else: pt_type_combo.addItem(display_type) pt_type_combo.setCurrentText(display_type) self.table.setCellWidget(row, rows.pt_type, pt_type_combo) iq_combo = QComboBox() iq_combo.addItems(iq_types) iq_type = get_iq_define(var['type']) # Получаем IQ-тип, например 'iq24' display_type = iq_type.replace('t_', '') if iq_type in iq_types: iq_combo.setCurrentText(display_type) else: iq_combo.addItem(display_type) iq_combo.setCurrentText(display_type) self.table.setCellWidget(row, rows.iq_type, iq_combo) ret_combo = QComboBox() ret_combo.addItems(iq_types) self.table.setCellWidget(row, rows.ret_type, ret_combo) short_name_edit = QLineEdit(var['name']) self.table.setCellWidget(row, rows.short_name, short_name_edit) cb.stateChanged.connect(self.save_table_to_xml) name_edit.textChanged.connect(self.save_table_to_xml) pt_type_combo.currentTextChanged.connect(self.save_table_to_xml) iq_combo.currentTextChanged.connect(self.save_table_to_xml) ret_combo.currentTextChanged.connect(self.save_table_to_xml) short_name_edit.textChanged.connect(self.save_table_to_xml) def save_table_to_xml(self): def make_relative_path(abs_path, base_path): try: return os.path.relpath(abs_path, base_path).replace("\\", "/") except ValueError: return abs_path.replace("\\", "/") xml_path = self.xml_output_edit.text().strip() proj_path = self.proj_path_edit.text().strip() makefile_path = self.makefile_edit.text().strip() if not xml_path or not os.path.isfile(xml_path): print("XML файл не найден или путь пустой") return try: tree = ET.parse(xml_path) root = tree.getroot() # Обновим атрибуты с относительными путями if os.path.isdir(proj_path): root.set("proj_path", proj_path.replace("\\", "/")) if os.path.isfile(makefile_path): rel_makefile = make_relative_path(makefile_path, proj_path) root.set("makefile_path", rel_makefile) if self.structs_xml_path and os.path.isfile(self.structs_xml_path): rel_struct_path = make_relative_path(self.structs_xml_path, proj_path) root.set("structs_path", rel_struct_path) vars_elem = root.find('variables') if vars_elem is None: # Если блока нет, создаём vars_elem = ET.SubElement(root, 'variables') original_info = {} for var_elem in vars_elem.findall('var'): name = var_elem.attrib.get('name') if name: original_info[name] = { 'type': var_elem.findtext('type', ''), 'file': var_elem.findtext('file', ''), 'extern': var_elem.findtext('extern', ''), 'static': var_elem.findtext('static', '') } # Собираем данные из таблицы updated_vars = [] for row in range(self.table.rowCount()): cb = self.table.cellWidget(row, 0) name_edit = self.table.cellWidget(row, 1) pt_type_combo = self.table.cellWidget(row, 3) iq_combo = self.table.cellWidget(row, 4) ret_combo = self.table.cellWidget(row, 5) short_name_edit = self.table.cellWidget(row, 6) var_name = name_edit.text() # Берём оригинальные type и file из словаря, если есть orig_type = original_info.get(var_name, {}).get('type', '') orig_file = original_info.get(var_name, {}).get('file', '') orig_extern = original_info.get(var_name, {}).get('extern', '') orig_static = original_info.get(var_name, {}).get('static', '') updated_vars.append({ 'name': var_name, 'enable': cb.isChecked(), 'shortname': short_name_edit.text(), 'pt_type': 'pt_' + pt_type_combo.currentText(), 'iq_type': iq_combo.currentText(), 'return_type': ret_combo.currentText() or 'int', 'type': orig_type, 'file': orig_file, 'extern': orig_extern, 'static': orig_static, }) # Обновляем или добавляем по одному var в XML for v in updated_vars: var_elem = None for ve in vars_elem.findall('var'): if ve.attrib.get('name') == v['name']: var_elem = ve break if var_elem is None: var_elem = ET.SubElement(vars_elem, 'var', {'name': v['name']}) def set_sub_elem_text(parent, tag, text): el = parent.find(tag) if el is None: el = ET.SubElement(parent, tag) el.text = str(text) set_sub_elem_text(var_elem, 'enable', 'true' if v['enable'] else 'false') set_sub_elem_text(var_elem, 'shortname', v['shortname']) set_sub_elem_text(var_elem, 'pt_type', v['pt_type']) set_sub_elem_text(var_elem, 'iq_type', v['iq_type']) set_sub_elem_text(var_elem, 'return_type', v['return_type']) set_sub_elem_text(var_elem, 'type', v['type']) set_sub_elem_text(var_elem, 'file', v['file']) set_sub_elem_text(var_elem, 'extern', v['extern']) set_sub_elem_text(var_elem, 'static', v['static']) # Сохраняем изменения tree.write(xml_path, encoding='utf-8', xml_declaration=True) except Exception as e: print(f"Ошибка при сохранении XML: {e}") if __name__ == "__main__": app = QApplication(sys.argv) editor = VarEditor() editor.resize(900, 600) editor.show() sys.exit(app.exec())